Now that you have a keypad module, you can add the final bit of logic for a complete petrol pump and bring this chapter to a close: the new Preset class/module in listing 4.13 causes the pump to stop pumping at the preset number of dollars.
Here are the inputs:
■ Preset value
■ Fuel price
■ Dollars delivered
■ Liters delivered
■ Fuel flowing—Whether the nozzle is lifted, and what fuel is being pumped.
■ Fill active—Fuel is flowing or has flowed, but the fill hasn’t been cleared by the point-of-sale system yet.
Here are the outputs:
■ Delivery—Which fuel to pump, and whether to go fast or slow
■ Keypad active—Whether the user can type on the keypad This is the logic you want:
■ 0 means no preset number of dollars.
■ You start pumping slowly just before the preset dollar value is reached.
■ You stop when the preset dollar value is reached.
■ The user can change the preset value any time before pumping slows; then the keypad is locked.
package chapter4.section11;
import pump.*;
import chapter4.section7.Fill;
import nz.sodium.*;
import java.util.Optional;
public class Preset {
public final Cell<Delivery> delivery;
public final Cell<Boolean> keypadActive;
public enum Speed { FAST, SLOW, STOPPED };
public Preset(Cell<Integer> presetDollars, Fill fi,
Cell<Optional<Fuel>> fuelFlowing, Cell<Boolean> fillActive) {
Cell<Speed> speed = presetDollars.lift(
fi.price, fi.dollarsDelivered, fi.litersDelivered,
(presetDollars_, price, dollarsDelivered, litersDelivered) -> { if (presetDollars_ == 0)
return Speed.FAST;
else {
if (dollarsDelivered >= (double)presetDollars_) return Speed.STOPPED;
double slowLiters =
(double)presetDollars_/price - 0.10;
if (litersDelivered >= slowLiters) return Speed.SLOW;
else
return Speed.FAST;
} });
delivery = fuelFlowing.lift(speed, (of, speed_) ->
speed_ == Speed.FAST ? (
of.equals(Optional.of(Fuel.ONE)) ? Delivery.FAST1 : of.equals(Optional.of(Fuel.TWO)) ? Delivery.FAST2 : of.equals(Optional.of(Fuel.THREE)) ? Delivery.FAST3 : Delivery.OFF ) :
speed_ == Speed.SLOW ? (
of.equals(Optional.of(Fuel.ONE)) ? Delivery.SLOW1 : of.equals(Optional.of(Fuel.TWO)) ? Delivery.SLOW2 : of.equals(Optional.of(Fuel.THREE)) ? Delivery.SLOW3 : Delivery.OFF ) :
Delivery.OFF);
Listing 4.13 Logic for a preset dollar amount
Calculates the pump speed
Converts the pump speed/fuel to an instruction for the motors
91 Adding a preset dollar amount
keypadActive = fuelFlowing.lift(speed, (of, speed_) ->
!of.isPresent() || speed_ == Speed.FAST);
} }
All the inputs and outputs to Preset are cells, and the only primitive you use is to combine them in different ways with lift. You first work out what speed to fill by encoding an enum of FAST, SLOW, or STOPPED. You combine that information with the fuelFlowing state to give a Delivery value to the pump’s motors. Finally, you decide whether the keypad should be active. You can change the preset amount any time before the motors switch to slow delivery.
The following listing shows the completed petrol pump.
package chapter4.section11;
import pump.*;
import chapter4.section4.LifeCycle;
import chapter4.section4.LifeCycle.End;
import chapter4.section6.AccumulatePulsesPump;
import chapter4.section7.Fill;
import chapter4.section7.ShowDollarsPump;
import chapter4.section8.NotifyPointOfSale;
import chapter4.section9.Keypad;
import nz.sodium.*;
import java.util.Optional;
public class PresetAmountPump implements Pump { public Outputs create(Inputs inputs) {
StreamLoop<Fuel> sStart = new StreamLoop<>();
Fill fi = new Fill(inputs.sClearSale.map(u -> Unit.UNIT), inputs.sFuelPulses, inputs.calibration, inputs.price1, inputs.price2, inputs.price3, sStart);
NotifyPointOfSale np = new NotifyPointOfSale(
new LifeCycle(inputs.sNozzle1, inputs.sNozzle2, inputs.sNozzle3), inputs.sClearSale,
fi);
sStart.loop(np.sStart);
CellLoop<Boolean> keypadActive = new CellLoop<>();
Keypad ke = new Keypad(inputs.sKeypad, inputs.sClearSale,
keypadActive);
Preset pr = new Preset(ke.value, fi,
np.fuelFlowing,
np.fillActive.map(o -> o.isPresent()));
keypadActive.loop(pr.keypadActive);
Listing 4.14 Completed petrol pump implementation with preset If not active,
keypad keys are ignored.
From preset() Preset value
from the keypad
return new Outputs()
.setDelivery(pr.delivery) .setSaleCostLCD(fi.dollarsDelivered.map(
q -> Formatters.formatSaleCost(q))) .setSaleQuantityLCD(fi.litersDelivered.map(
q -> Formatters.formatSaleQuantity(q)))
.setPriceLCD1(ShowDollarsPump.priceLCD(np.fillActive, fi.price, Fuel.ONE, inputs))
.setPriceLCD2(ShowDollarsPump.priceLCD(np.fillActive, fi.price, Fuel.TWO, inputs))
.setPriceLCD3(ShowDollarsPump.priceLCD(np.fillActive, fi.price, Fuel.THREE, inputs))
.setSaleComplete(np.sSaleComplete) .setPresetLCD(ke.value.map(v ->
Formatters.formatSaleCost((double)v))) .setBeep(np.sBeep.orElse(ke.sBeep));
} }
You feed Preset’s keypadActive into the Keypad module. Because this is called before you construct Preset, you use your old friend CellLoop to loop it.
There are now two sources of beeps: NotifyPointOfSale beeps when the sale is cleared, and Keypad beeps when a key is pressed. You must return the merge of these two in Outputs.