Silent sensors: advancing time
A Detector evaluates its Rules only when something happens. If Samples stop arriving, nothing happens: a duration is never reached, and a sensor that has fallen silent goes unnoticed. advanceTo(timestamp) tells the Detector that time has passed without new data, typically called by a timer.
Time without Samples
x.age() is the number of milliseconds since x last had a value, measured at the current Sample or advance; before the first value it is undefined. At a Sample that carries x it is 0. It grows at Samples that do not carry x and at advances, so when no Samples arrive at all, only advances make it grow:
Rule sensorLost = Rule.named("SensorLost")
.when(hr.age().above(Duration.ofSeconds(10).toMillis()));
Detector detector = Detector.builder()
.rules(sensorLost)
.build();
detector.push(0, Map.of(hr, 72.0));
detector.advanceTo(5_000); // hr is 5 s old
detector.advanceTo(12_000); // hr is 12 s old: SensorLost Confirmed
detector.push(13_000, Map.of(hr, 70.0)); // SensorLost Ended at 13000
A heart rate that falls silent
A heart rate is reported every second. From 12 s it is high, and from 25 to 44 s the sensor is silent. HighHeartRate trusts a high reading only while it is recent: hr above 120 and not older than the limit. SensorLost is the opposite, a reading older than the limit. Without a Sample, a timer advances time once a second, and rbed computes hr.age() at every Sample and every advance.
The live chart and its controls need JavaScript. The tables show the Events at the default parameters.
The site cannot run the default parameters: (JavaScript) TypeError: Cannot read properties of undefined (reading 'length')
import io.github.romanhaendler.rbed.*;
import java.time.Duration;
import java.util.Map;
Channel hr = Channel.named("hr");
Condition silent = hr.age().above(Duration.ofSeconds(5).toMillis());
Rule highHeartRate = Rule.named("HighHeartRate").when(hr.above(120).and(silent.not()));
Rule sensorLost = Rule.named("SensorLost").when(silent);
Detector detector = Detector.builder()
.rules(highHeartRate, sensorLost)
.build();
for (long s = 0; s <= 60; s++) { // every second
boolean reports = s < 25 || s >= 45; // the sensor is silent from 25 to 44 s
double reading = s < 12 ? 78 + s % 3 : s < 25 ? 128 + s % 4 : 92 - s % 3;
if (reports) detector.push(s * 1_000, Map.of(hr, reading));
else detector.advanceTo(s * 1_000); // the timer: time passes without a Sample
}
detector.close();
At the defaults, with the timer, hr.age() grows by a second at every advance after the last reading at 24 s. At 30 s it is 6 s old, more than the limit of 5 s: HighHeartRate Ends there and SensorLost is Confirmed, both without a Sample. SensorLost Ends at 45 s, when the sensor reports again.
Things to try
- Raise Silent after to 10 s: both move later, to 35 s.
- Choose "Nothing happens" instead of the timer: the Detector learns of the silence only when the next Sample arrives at 45 s. That Sample carries
hr, so its age is 0 again, andhr.age(), read only at Samples, is 0 at every one of them: there is noSensorLostEvent, andHighHeartRateruns through the whole silence on a reading that is 21 seconds old by the time the next one arrives.
What an advance does
At an advance, every Rule is evaluated with each Channel holding its last value (a Pulse Channel is absent): durations can be reached, totalDuration and count(...).within(...) move on, and a maxGap is noticed. An advance is not a Sample, though: it does not count for forSamples, nor for the Sample-based Aggregates min, max, mean and count. The time-weighted Aggregates do include the time up to it.
An advance must lie after the previous Sample or advance, and a Detector with a sampling rate rejects it, because there time advances with the Samples.
Reference
- Silent sensors: advancing time in the README, the canonical text
- Detector in the Javadoc:
advanceTo - The Healthcare example: a sensor dropout during which the monitor advances its Detector every second
Methods used: Channel.named, ChannelExpression.age, Expression.above, Rule.named, Rule.when, Condition.and, Condition.not, Detector.builder, Detector.Builder.rules, Detector.Builder.build, Detector.push, Detector.advanceTo, Detector.close.