001package jmri.jmrit.tracker;
002
003import jmri.Block;
004import jmri.SignalHead;
005import jmri.Throttle;
006
007/**
008 * Stop a train in a block if required.
009 * <p>
010 * Watches a Block object that is passing around a Throttle object as its
011 * value. When the Block goes OCCUPIED, check whether a signal is telling the
012 * train to stop; if so, force the Throttle to zero speed.
013 * <p>
014 * This contains multiple SignalHead objects, each associated with a Path that
015 * contains one or more BeanSettings (e.g. Turnout positions) and directions.
016 * When needed, this consults the paths to see which one is active (has its
017 * Turnouts set) and corresponds to the current direction of the block. There
018 * should be exactly one of these, which will then identify which signal to
019 * monitor.
020 * <p>
021 * Limitations:
022 * <ul>
023 * <li>Current implementation does not protect against changing direction and
024 * backing out of the block
025 * <li>Should track speed at time of stop and restore it on restart (or should
026 * it not restart? Optional restart?)
027 * </ul>
028 *
029 * @author Bob Jacobsen Copyright (C) 2006
030 */
031public class StoppingBlock {
032
033    public StoppingBlock(Block b) {
034        block = b;
035
036        // set a listener in the block
037        block.addPropertyChangeListener(this::handleBlockChange);
038    }
039
040    void handleBlockChange(java.beans.PropertyChangeEvent e) {
041        // check for going occupied
042        if ( Block.PROPERTY_STATE.equals(e.getPropertyName()) && e.getNewValue().equals(Block.OCCUPIED)) {
043            if (sig1 == null) {
044                return;
045            }
046
047            if (direction != block.getDirection()) {
048                return;  // no interesting
049            }
050            int val = fastestAppearance();
051            if (log.isDebugEnabled()) {
052                log.debug("Block {} occupied with {}", block.getSystemName(), val);
053            }
054
055            if (val == SignalHead.RED) {
056                doStop();
057            }
058            if (val == SignalHead.YELLOW) {
059                doSlow();
060            }
061        }
062    }
063
064    void handleSignalChange(java.beans.PropertyChangeEvent e) {
065        // if currently have a loco present and stopped,
066        // consider changing speed
067        if ((block.getValue() != null) && block.getState() == (Block.OCCUPIED)) {
068
069            if (sig1 == null) {
070                return;
071            }
072
073            if (direction != block.getDirection()) {
074                return;  // not interesting
075            }
076            int val = fastestAppearance();
077            log.debug("Block {} signal change to {}", block, val);
078
079            if (val == SignalHead.YELLOW) {
080                doSlow();
081            }
082            if (val == SignalHead.GREEN) {
083                doRestart();
084            }
085        }
086    }
087
088    public void addSignal(SignalHead s, int dir) {
089        sig1 = s;
090        direction = dir;
091
092        sig1.addPropertyChangeListener(this::handleSignalChange);
093    }
094
095    public void addSignal(SignalHead s1, SignalHead s2, int dir) {
096        addSignal(s1, dir);
097        sig2 = s2;
098        sig2.addPropertyChangeListener(this::handleSignalChange);
099    }
100
101    int fastestAppearance() {
102        if (sig1 == null) {
103            log.error("Should not get null in fastestAppearance");
104            return 0;
105        }
106        if (sig2 == null) {
107            return sig1.getAppearance();
108        } else {
109            return Math.max(sig1.getAppearance(), sig2.getAppearance());
110        }
111    }
112
113    /**
114     * Perform the stop operation
115     */
116    void doStop() {
117        log.debug("Block {} speed being set to stop", block.getDisplayName());
118        setSpeed(0.0f, false, false, false);  // bell on
119    }
120
121    void doSlow() {
122        log.debug("Block {} speed being set to slow", block.getDisplayName());
123        setSpeed(slow, false, false, false);  // bell off
124    }
125
126    void doRestart() {
127        log.debug("Block {} speed being set to run", block.getDisplayName());
128        setSpeed(fast, false, false, false);  // bell off
129    }
130
131    void setSpeed(float speed, boolean f1, boolean f2, boolean f3) {
132        Object o = block.getValue();
133        if (o == null) {
134            log.error("Block {} contained no Throttle object", block.getDisplayName());
135            return;
136        }
137        if ( o instanceof Throttle ) {
138            Throttle t = (Throttle) o;
139            t.setSpeedSetting(speed);
140            t.setFunction(1, f1);
141            t.setFunction(2, f2);
142        } else {
143            log.error("Block {} did not contain object of Throttle type, was {} , a {}",
144                block.getDisplayName(), o, o.getClass());
145        }
146    }
147
148    /**
149     * Set speeds.
150     * @param s the slow speed, default 0.3
151     * @param f the fast speed, default 0.6
152     */
153    public void setSpeeds(float s, float f) {
154        slow = s;
155        fast = f;
156    }
157
158    // data members
159    Block block;
160    SignalHead sig1;
161    SignalHead sig2;
162    int direction;
163    private float slow = 0.3f;
164    private float fast = 0.6f;
165
166    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(StoppingBlock.class);
167
168}