rbed

Samples and Channels

A Sample is the values of one or more Channels at one timestamp. Timestamps are milliseconds (long), for example epoch milliseconds or milliseconds since the start of a recording, and they must strictly increase: a Sample at or before the previous one is rejected.

Channels

Each Channel is created once, by name, and then used everywhere: in Conditions, when pushing values and when reading Aggregates. The name is written in one place, and the compiler checks every use. In an application, Channels are typically static final constants.

Channel temp = Channel.named("temp");
Channel humidity = Channel.named("humidity");

Channels are equal by name, so data that arrives with names as strings, such as a CSV row or a JSON object, maps onto the same Channels without a registry:

Map<Channel, Double> values = new HashMap<>();
row.forEach((column, value) -> values.put(Channel.named(column), value));
detector.push(timestamp, values);

Values of Channels that no Rule uses are ignored, so a Sample may carry more Channels than the Rules need. Values must be finite: a NaN or infinite value is rejected.

Sensors at different rates

A Sample does not have to contain every Channel. A Channel missing from a Sample keeps its last value, so sensors reporting at different rates can share one Detector. Here a room's temperature temp reports every second and rises from 21 °C to 30 °C. Its humidity reports less often; it rises from 50 % to 65 % at 30 s and falls again. Muggy is an Event while the temperature is above 26 °C and the humidity above 60 %. Between two reports, humidity is the value of the last one.

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.util.HashMap;
import java.util.Map;

Channel temp = Channel.named("temp");
Channel humidity = Channel.named("humidity");

Rule muggy = Rule.named("Muggy").when(temp.above(26).and(humidity.above(60)));
Detector detector = Detector.builder()
    .rules(muggy)
    .build();

for (int s = 0; s <= 60; s++) {
  Map<Channel, Double> values = new HashMap<>();
  values.put(temp, Math.min(30, 21 + 0.5 * s));   // temp: every second
  if (s % 10 == 0) {   // humidity: every 10 s
    values.put(humidity, s <= 30 ? 50 + 0.5 * s : 65 - 0.5 * (s - 30));
  }
  detector.push(s * 1_000L, values);   // a missing Channel keeps its last value
}
detector.close();

At the default of a humidity report every 10 seconds:

  • temp is above 26 °C from 11 s, but humidity is not above 60 % in any report until the one at 30 s (65 %). Muggy starts there, although the humidity actually rose above 60 % at 21 s: rbed knows only the values it is given.
  • From 31 s to 39 s the Samples carry only temp, and humidity keeps its 65 %. The report at 40 s reads exactly 60 %, which is not above 60, and Muggy Ends.

Things to try

  • Report the humidity every second: Muggy runs from 21 s to 40 s, where the humidity really is above 60 %.
  • Report it every 7 seconds: the reports from 21 s to 35 s are above 60 %, and the next one comes at 42 s. Muggy runs from 21 s until 42 s, two seconds longer than the humidity stayed above 60 %, because the held value says so.

A Channel that has had no value yet is undefined, and a Condition on it never holds (see Conditions and Expressions). To bound how long a held value may be trusted, use maxGap for the gap between Samples or age() for a single Channel.

Reference

Methods used: Channel.named, Rule.named, Rule.when, Expression.above, Condition.and, Detector.builder, Detector.Builder.rules, Detector.Builder.build, Detector.push, Detector.close.