# 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.

Parameters (in the page address as ?lift=…):

- Lifted above (`lift`): 100 to 2000 kg in steps of 50, default 500
- Lifted for (`liftSeconds`): 1 to 5 s in steps of 1, default 2
- Overload for (`overloadSamples`): 1 to 6 Samples in steps of 1, default 2
- Partial set-down below (`partial`): 0.1 to 0.9 × max in steps of 0.05, default 0.5

The site cannot run the default parameters: (JavaScript) TypeError: Cannot read properties of undefined (reading 'length')

Java source at the default parameters:

```java
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](https://github.com/romanhaendler/rbed/blob/main/src/test/java/io/github/romanhaendler/rbed/CraneScenarioTest.java), a test with its expected Events, listed in the README's [Examples](https://github.com/romanhaendler/rbed#examples).

Methods used: [`Channel.named`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Channel.html#named%28java.lang.String%29), [`Rule.named`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#named%28java.lang.String%29), [`Rule.when`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#when%28io.github.romanhaendler.rbed.Condition%29), `Expression.above` ([double](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Expression.html#above%28double%29), [Expression](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Expression.html#above%28io.github.romanhaendler.rbed.Expression%29)), [`Rule.forDuration`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#forDuration%28java.time.Duration%29), [`Rule.endWhen`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#endWhen%28io.github.romanhaendler.rbed.Condition%29), `Expression.below` ([double](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Expression.html#below%28double%29), [Expression](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Expression.html#below%28io.github.romanhaendler.rbed.Expression%29)), [`Rule.maxGap`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#maxGap%28java.time.Duration%29), [`Condition.and`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Condition.html#and%28io.github.romanhaendler.rbed.Condition%29), [`Conditions.during`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Conditions.html#during%28io.github.romanhaendler.rbed.Rule%29), [`Rule.forSamples`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#forSamples%28int%29), [`Conditions.latest`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Conditions.html#latest%28io.github.romanhaendler.rbed.Rule%29), [`LatestEvent.max`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/LatestEvent.html#max%28io.github.romanhaendler.rbed.Channel%29), [`Expression.times`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Expression.html#times%28double%29), [`Detector.builder`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#builder%28%29), [`Detector.Builder.rules`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#rules%28io.github.romanhaendler.rbed.Rule...%29), [`Detector.Builder.listener`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#listener%28io.github.romanhaendler.rbed.EventListener%29), [`EventListener.onEnded`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/EventListener.html#onEnded%28io.github.romanhaendler.rbed.Event%29), [`Event.is`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Event.html#is%28io.github.romanhaendler.rbed.Rule%29), [`Detector.Builder.build`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#build%28%29), [`Detector.push`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#push%28long,java.util.Map%29), [`Detector.close`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#close%28%29), [`Event.aggregates`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Event.html#aggregates%28io.github.romanhaendler.rbed.Channel%29), [`Aggregates.mean`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Aggregates.html#mean%28%29).

The concepts at work, each on a page of its own: [End Conditions and hysteresis](https://romanhaendler.github.io/rbed/end-conditions/), [Data gaps](https://romanhaendler.github.io/rbed/data-gaps/), [During another Event](https://romanhaendler.github.io/rbed/during/), [The latest Event](https://romanhaendler.github.io/rbed/latest/), [Aggregates](https://romanhaendler.github.io/rbed/aggregates/).
