001package jmri.jmrix.maple;
002
003import jmri.Turnout;
004import jmri.implementation.AbstractTurnout;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Turnout implementation for Maple systems.
010 * <p>
011 * This object doesn't listen to the interface communications. This is because
012 * it should be the only object that is sending messages for this turnout; more
013 * than one Turnout object pointing to a single device is not allowed.
014 * <p>
015 * Turnouts on the layout may be controlled by one or two output bits. To
016 * control a turnout from one Turnout object via two output bits, the output
017 * bits must be on the same node, the Turnout address must point to the first
018 * output bit, and the second output bit must follow the output bit at the next
019 * address. Valid states for the two bits controlling the two-bit turnout are:
020 * ON OFF, and OFF ON for the two bits.
021 * <p>
022 * This class can also drive pulsed outputs, which can be combined with the
023 * two-bit option in the expected ways.
024 * <p>
025 * When a Turnout is configured for pulsed and two-output, a request to go to a
026 * new CommandedState sets the desired configuration for the pulse interval,
027 * then sets both leads to their off condition.
028 * <p>
029 * When a Turnout is configured for pulsed and one output, a request to go to a
030 * new CommandedState just sets the output on for the interval; it's assumed
031 * that there's something out on the layout that converts that pulse into a
032 * "flip to other state" operation.
033 * <p>
034 * Finally, this implementation supports the "inverted" option. Inverted applies
035 * to the status of the lead on the output itself.
036 * <p>
037 * For example, a pulsed, two-output, inverted turnout will have both pins set
038 * to 1 in the resting state. When THROWN, one lead will be set to 0 for the
039 * configured interval, then set back to 1.
040 * <p>
041 * For more discussion of this, please see the
042 * <a href="http://jmri.org/help/en/html/hardware/cmri/CMRI.shtml#options">documentation
043 * page</a>
044 * (for C/MRI, but this is similar).
045 *
046 * NOTE: In the current version Maple support, code for implementing pulsed
047 * turnouts has been commented out.
048 *
049 * @author Bob Jacobsen Copyright (C) 2003, 2007, 2008
050 * @author David Duchamp Copyright (C) 2004, 2007
051 * @author Dan Boudreau Copyright (C) 2007
052 */
053public class SerialTurnout extends AbstractTurnout {
054
055    private MapleSystemConnectionMemo _memo = null;
056
057    /**
058     * Create a Turnout object, with both system and user names.
059     * <p>
060     * 'systemName' has already been validated in SerialTurnoutManager
061     *
062     * @param systemName the system name for this Turnout
063     * @param userName   the user name for this Turnout
064     * @param memo       the memo for the system connection
065     */
066    public SerialTurnout(String systemName, String userName, MapleSystemConnectionMemo memo) {
067        super(systemName, userName);
068        // Save system Name
069        tSystemName = systemName;
070        _memo = memo;
071        // Extract the Bit from the name
072        tBit = SerialAddress.getBitFromSystemName(systemName, _memo.getSystemPrefix());
073    }
074
075    /**
076     * Create a Turnout object, with only a system name.
077     * <p>
078     * 'systemName' has already been validated in SerialTurnoutManager
079     *
080     * @param systemName the system name for this Turnout
081     * @param memo       the memo for the system connection
082     */
083    public SerialTurnout(String systemName, MapleSystemConnectionMemo memo) {
084        super(systemName);
085        // Save system Name
086        tSystemName = systemName;
087        _memo = memo;
088        // Extract the Bit from the name
089        tBit = SerialAddress.getBitFromSystemName(systemName, _memo.getSystemPrefix());
090    }
091
092    /**
093     * {@inheritDoc}
094     */
095    @Override
096    protected void forwardCommandChangeToLayout(int newState) {
097        try {
098            sendMessage(stateChangeCheck(newState));
099        } catch (IllegalArgumentException ex) {
100            log.error("new state invalid, Turnout not set");
101        }
102    }
103
104    /**
105     * Turnouts do support inversion.
106     */
107    @Override
108    public boolean canInvert() {
109        return true;
110    }
111
112    @Override
113    protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) {
114        if (log.isDebugEnabled()) {
115            log.debug("Send command to {} Pushbutton", (_pushButtonLockout ? "Lock" : "Unlock"));
116        }
117    }
118
119    // data members
120    String tSystemName; // System Name of this turnout
121    protected int tBit;   // bit number of turnout control in Serial node
122// protected SerialNode tNode = null;
123    protected javax.swing.Timer mPulseClosedTimer = null;
124    protected javax.swing.Timer mPulseThrownTimer = null;
125    protected boolean mPulseTimerOn = false;
126
127    /**
128     * Control the actual layout hardware. The request is for a particular
129     * functional setting, e.g. CLOSED or THROWN. The "inverted" status of the
130     * output leads is handled here.
131     * @param closed true sets the Turnout to CLOSED
132     */
133    protected void sendMessage(boolean closed) {
134        // if a Pulse Timer is running, ignore the call
135        if (!mPulseTimerOn) {
136            if (getNumberControlBits() == 1) {
137                // check for pulsed control
138                if (getControlType() == 0) {
139                    // steady state control, get current status of the output bit
140                    if ((_memo.getTrafficController().outputBits().getOutputBit(tBit) ^ getInverted()) != closed) {
141                        // bit state is different from the requested state, set it
142                        _memo.getTrafficController().outputBits().setOutputBit(tBit, closed ^ getInverted());
143                    } else {
144                        // Bit state is the same as requested state, so nothing
145                        // will happen if requested state is set.
146                        // Check if turnout known state is different from requested state
147                        int kState = getKnownState();
148                        if (closed) {
149                            // CLOSED is being requested
150                            if ((kState & Turnout.THROWN) != 0) {
151                                // known state is different from output bit, set output bit to be correct
152                                //     for known state, then start a timer to set it to requested state
153                                _memo.getTrafficController().outputBits().setOutputBit(tBit, false ^ getInverted());
154//        // start a timer to finish setting this turnout
155//        if (mPulseClosedTimer==null) {
156//         mPulseClosedTimer = new javax.swing.Timer(OutputBits.instance().getPulseWidth(),
157//           new java.awt.event.ActionListener() {
158//          public void actionPerformed(java.awt.event.ActionEvent e) {
159//           OutputBits.instance().setOutputBit(tBit, true^getInverted());
160//           mPulseClosedTimer.stop();
161//           mPulseTimerOn = false;
162//          }
163//         });
164//        }
165//        mPulseTimerOn = true;
166//        mPulseClosedTimer.start();
167                            }
168                        } else {
169                            // THROWN is being requested
170                            if ((kState & Turnout.CLOSED) != 0) {
171                                // known state is different from output bit, set output bit to be correct
172                                //     for known state, then start a timer to set it to requested state
173                                _memo.getTrafficController().outputBits().setOutputBit(tBit, true ^ getInverted());
174//        // start a timer to finish setting this turnout
175//        if (mPulseThrownTimer==null) {
176//         mPulseThrownTimer = new javax.swing.Timer(OutputBits.instance().getPulseWidth(),
177//           new java.awt.event.ActionListener() {
178//          public void actionPerformed(java.awt.event.ActionEvent e) {
179//           OutputBits.instance().setOutputBit(tBit, false^getInverted());
180//           mPulseThrownTimer.stop();
181//           mPulseTimerOn = false;
182//          }
183//         });
184//        }
185//        mPulseTimerOn = true;
186//        mPulseThrownTimer.start();
187                            }
188                        }
189                    }
190                } else {
191                    // Pulse control
192//     int iTime = OutputBits.instance().getPulseWidth();
193//     // Get current known state of turnout
194//     int kState = getKnownState();
195//     if ( (closed && ((kState & Turnout.THROWN) != 0)) ||
196//       (!closed && ((kState & Turnout.CLOSED) != 0)) ) {
197//      // known and requested are different, a change is requested
198//      //   Pulse the line, first turn bit on
199//      OutputBits.instance().setOutputBit(tBit,false^getInverted());
200//      // Start a timer to return bit to off state
201//      if (mPulseClosedTimer==null) {
202//       mPulseClosedTimer = new javax.swing.Timer(iTime, new
203//          java.awt.event.ActionListener() {
204//        public void actionPerformed(java.awt.event.ActionEvent e) {
205//         OutputBits.instance().setOutputBit(tBit, true^getInverted());
206//         mPulseClosedTimer.stop();
207//         mPulseTimerOn = false;
208//        }
209//       });
210//      }
211//      mPulseTimerOn = true;
212//      mPulseClosedTimer.start();
213//     }
214                }
215            }
216        }
217    }
218
219    private final static Logger log = LoggerFactory.getLogger(SerialTurnout.class);
220
221}