rbed

Cranes

A crane's load cell reports the weight on the hook every 500 ms, and the crane's rated load arrives once, as the Channel ratedLoad (5000 kg) in the first Sample, and holds from then on. Here a pallet is lifted and set down, then a container heavier than the rated load is lifted, partly set down (to less than half of its weight) and set down completely.

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 static io.github.romanhaendler.rbed.Conditions.*;

import io.github.romanhaendler.rbed.*;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

Channel ratedLoad = Channel.named("ratedLoad");
Channel weight = Channel.named("weight");

Rule loadLifted = Rule.named("LoadLifted")
    .when(weight.above(500)).forDuration(Duration.ofSeconds(2))
    .endWhen(weight.below(50)).forDuration(Duration.ofSeconds(1))
    .maxGap(Duration.ofSeconds(5));
Rule overload = Rule.named("Overload")
    .when(weight.above(ratedLoad).and(during(loadLifted)))
    .forSamples(2);
Rule partialSetDown = Rule.named("PartialSetDown")
    .when(during(loadLifted)
        .and(weight.below(latest(loadLifted).max(weight).times(0.5)))
        .and(weight.above(50)))  // still a load on the hook
    .forSamples(2);
Detector crane = Detector.builder()
    .rules(loadLifted, overload, partialSetDown)
    .listener(new EventListener() {
      @Override public void onEnded(Event e) {
        if (e.is(loadLifted)) System.out.println("lifted a " + load(e, weight));
      }
    })
    .build();

for (long t = 0; t <= 14_000; t += 500) {   // the weight every 500 ms
  Map<Channel, Double> values = new HashMap<>(Map.of(weight, weightAt(t)));
  if (t == 0) values.put(ratedLoad, 5000.0);   // sent once, held from then on
  crane.push(t, values);
}
crane.close();

// In the enclosing class:
// A plain method classifies a lifted load by its mean weight
static String load(Event lift, Channel weight) {
  return lift.aggregates(weight).mean() < 1500 ? "pallet" : "container";
}

static double weightAt(long t) {
  if (t < 1_500) return 0;      // hook empty
  if (t < 6_500) return 1200;   // pallet
  if (t < 8_500) return 10;     // set down, empty hook swinging
  if (t < 11_500) return 6000;  // container
  if (t < 12_500) return 2500;  // part of it set down
  return 0;
}

The Rules

  • LoadLifted: above 500 kg for 2 s, over only once the weight is below 50 kg for 1 s (hysteresis); a gap of more than 5 s in the data Ends it.
  • Overload: the weight above the rated load during a lift, for 2 Samples.
  • PartialSetDown: during a lift, the weight below half of the lift's maximum so far (latest(loadLifted).max(weight)) but still above 50 kg, for 2 Samples.
  • A plain method, which the listener calls for each Ended lift, classifies the lift by its mean weight, shown as a note in the table: below 1500 kg a pallet, otherwise a container.

At the defaults, the pallet of 1200 kg is a LoadLifted from 1.5 s to 6.5 s, Confirmed at 3.5 s. The container is lifted from 8.5 s to 12.5 s and Confirmed at 10.5 s; its mean is 5125 kg, from 6 Samples at 6000 kg and 2 at 2500 kg. Its 6000 kg are above the rated load, so Overload runs from 10.5 s, once the lift is Confirmed, to 11.5 s. There the weight drops to 2500 kg, less than half of the 6000 kg, and PartialSetDown runs from 11.5 s to 12.5 s.

Things to try

  • Raise Lifted above to 1500 kg: the pallet of 1200 kg no longer counts as a lift, and only the container does.
  • Lower Partial set-down below to 0.3: 2500 kg is not below 30 % of the container's 6000 kg, and there is no PartialSetDown.

Reference

The example is CraneScenarioTest, a test with its expected Events, listed in the README's Examples.

Methods used: Channel.named, Rule.named, Rule.when, Expression.above (double, Expression), Rule.forDuration, Rule.endWhen, Expression.below (double, Expression), Rule.maxGap, Condition.and, Conditions.during, Rule.forSamples, Conditions.latest, LatestEvent.max, Expression.times, Detector.builder, Detector.Builder.rules, Detector.Builder.listener, EventListener.onEnded, Event.is, Detector.Builder.build, Detector.push, Detector.close, Event.aggregates, Aggregates.mean.

The concepts at work, each on a page of its own: End Conditions and hysteresis, Data gaps, During another Event, The latest Event, Aggregates.