001/*
002 *  @author Gregory J. Bedlek Copyright (C) 2018, 2019
003 */
004package jmri.jmrit.ctc;
005
006import java.awt.event.ActionListener;
007import java.beans.PropertyChangeEvent;
008import java.beans.PropertyChangeListener;
009import javax.swing.Timer;
010import jmri.Sensor;
011import jmri.Turnout;
012
013/*
014If you have REAL feedback from the switch that sets it's position:
015    You SHOULD set "Feedback" to "True" and rely on this message to set the switch in "correspondence".
016    If you didn't set "Feedback" to "True" in this case, and the timeout amount was shorter than
017    the time it took the switch to "feed back" it's information, the switch will be OUT OF CORRESPONDENCE
018    and both lights will REMAIN unlit.  In this case, throwing it AGAIN will eventually report proper!
019    If the "feedback" message from the unit is lost, then the lights will REMAIN (properly!) out of correspondence!
020
021This object ACTUALLY CONTROLS THE SWITCH, AND sends commands to move the points.
022*/
023
024public class SwitchDirectionIndicators {
025    private NBHSensor _mNormalIndicatorSensor;
026    private NBHSensor _mReversedIndicatorSensor;
027    private NBHTurnout _mActualTurnout;
028    private PropertyChangeListener _mActualTurnoutPropertyChangeListener = null;
029    private boolean _mActualTurnoutHasFeedback;
030    private boolean _mWaitingForFeedbackOrTimer = false;
031    private int _mLastActualTurnoutState = CTCConstants.CTC_UNKNOWN;
032    private Timer _mSimulatedTurnoutFeedbackTimer = null;
033    private ActionListener _mSimulatedTurnoutFeedbackTimerActionListener = null;
034    private int _mLastIndicatorState = CTCConstants.OUTOFCORRESPONDENCE;
035
036    public SwitchDirectionIndicators(   String userIdentifier,
037                                        NBHSensor normalIndicatorSensor,
038                                        NBHSensor reveresedIndicatorSensor,
039                                        NBHTurnout actualTurnout,
040                                        int codingTimeInMilliseconds,           // Instead of "CodingDistrict"
041                                        boolean feedbackDifferent) {
042        _mNormalIndicatorSensor = normalIndicatorSensor;
043        _mReversedIndicatorSensor = reveresedIndicatorSensor;
044        _mActualTurnout = actualTurnout;
045        _mActualTurnoutHasFeedback = _mActualTurnout.getFeedbackMode() != Turnout.DIRECT && _mActualTurnout.getFeedbackMode() != Turnout.MONITORING;
046
047        if (_mActualTurnoutHasFeedback) {
048            // Let real sensor that drives turnout feedback set indicators:
049            _mActualTurnoutPropertyChangeListener = (PropertyChangeEvent e) -> { if (e.getPropertyName().equals("KnownState")) setSwitchIndicationSensorsToPresentState(); };   // NOI18N
050            _mActualTurnout.addPropertyChangeListener(_mActualTurnoutPropertyChangeListener);
051            setSwitchIndicationSensorsToPresentState();
052        } else { // Simulate feedback delay if any:
053            _mSimulatedTurnoutFeedbackTimerActionListener = (ActionEvent) -> { setSwitchIndicationSensorsToPresentState(); };
054            _mSimulatedTurnoutFeedbackTimer = new Timer(codingTimeInMilliseconds, _mSimulatedTurnoutFeedbackTimerActionListener);
055            _mSimulatedTurnoutFeedbackTimer.setRepeats(false);
056/*      IF AND ONLY IF the layout owner DOES NOT initialize all DIRECT or MONITORING turnouts BEFORE
057        starting this CTC system, we wind up with turnouts that have KnownState = UNKNOWN
058        Here, IF AND ONLY IF the layout owner initialized those turnouts prior to starting this CTC system,
059        we now "give a chance" for us to find out the real state, and update the state if know, otherwise,
060        we ASSUME it is SWITCHNORMAL for those people who don't.
061*/
062            setSwitchIndicationSensorsToPresentState();
063            if (_mLastActualTurnoutState == CTCConstants.CTC_UNKNOWN) {
064                setSwitchIndicatorSensors(CTCConstants.SWITCHNORMAL);
065            }
066        }
067    }
068
069    public void removeAllListeners() {
070        _mActualTurnout.removePropertyChangeListener(_mActualTurnoutPropertyChangeListener);
071        if (_mSimulatedTurnoutFeedbackTimer != null) {
072            _mSimulatedTurnoutFeedbackTimer.stop();
073            _mSimulatedTurnoutFeedbackTimer.removeActionListener(_mSimulatedTurnoutFeedbackTimerActionListener);
074        }
075    }
076
077    public void codeButtonPressed(int requestedState) {
078        if (_mWaitingForFeedbackOrTimer) return;    // We've already done something in the past, ignore new request
079        if (requestedState != _mLastActualTurnoutState) {   // It's different:
080            _mWaitingForFeedbackOrTimer = true;     // We're doing something now!
081            setSwitchIndicatorSensors(CTCConstants.OUTOFCORRESPONDENCE);
082            if (requestedState == CTCConstants.SWITCHNORMAL) { _mActualTurnout.setCommandedState(Turnout.CLOSED); }
083            else if (requestedState == CTCConstants.SWITCHREVERSED) { _mActualTurnout.setCommandedState(Turnout.THROWN); } // Any other passed invalid value is ignored.
084            if (!_mActualTurnoutHasFeedback) _mSimulatedTurnoutFeedbackTimer.start();   // Simulate feedback (fire timer only once)
085        }
086    }
087
088    private int getPresentState() {
089        int actualTurnoutKnownState = _mActualTurnout.getKnownState();
090        if (actualTurnoutKnownState == Turnout.CLOSED) _mLastActualTurnoutState = CTCConstants.SWITCHNORMAL;
091        else if (actualTurnoutKnownState == Turnout.THROWN) _mLastActualTurnoutState = CTCConstants.SWITCHREVERSED;
092        else _mLastActualTurnoutState = CTCConstants.CTC_UNKNOWN;
093        return _mLastActualTurnoutState;
094    }
095
096    public int getLastIndicatorState() {
097        return _mLastIndicatorState;
098    }
099
100    public boolean inCorrespondence() {
101        if (_mLastActualTurnoutState == CTCConstants.CTC_UNKNOWN) return true;   // Fake out calling routine to allow it to call us at "CodeButtonPressed"
102        return _mLastIndicatorState != CTCConstants.OUTOFCORRESPONDENCE;
103    }
104
105    public NBHSensor getProperIndicatorSensor(boolean isNormal) {
106        return isNormal ? _mNormalIndicatorSensor : _mReversedIndicatorSensor;
107    }
108
109    private void setSwitchIndicatorSensors(int requestedState) {
110        switch(requestedState) {
111            case CTCConstants.SWITCHNORMAL:
112                _mNormalIndicatorSensor.setKnownState(Sensor.ACTIVE);
113                _mReversedIndicatorSensor.setKnownState(Sensor.INACTIVE);
114                _mLastIndicatorState = CTCConstants.SWITCHNORMAL;
115                break;
116            case CTCConstants.SWITCHREVERSED:
117                _mNormalIndicatorSensor.setKnownState(Sensor.INACTIVE);
118                _mReversedIndicatorSensor.setKnownState(Sensor.ACTIVE);
119                _mLastIndicatorState = CTCConstants.SWITCHREVERSED;
120                break;
121            default:
122                _mNormalIndicatorSensor.setKnownState(Sensor.INACTIVE);
123                _mReversedIndicatorSensor.setKnownState(Sensor.INACTIVE);
124                _mLastIndicatorState = CTCConstants.OUTOFCORRESPONDENCE;
125                break;
126        }
127    }
128
129    private void setSwitchIndicationSensorsToPresentState() {
130        if (_mSimulatedTurnoutFeedbackTimer != null) _mSimulatedTurnoutFeedbackTimer.stop();
131        _mWaitingForFeedbackOrTimer = false;
132        setSwitchIndicatorSensors(getPresentState());
133    }
134
135//  private int getTransmissionDelayInMilliseconds(CodingDistrict codingDistrict) {
136//      if (codingDistrict != null) return codingDistrict.getTransmissionDelayInMilliseconds();
137//      return 0;
138//  }
139}