001package jmri.jmrix.nce;
002
003import jmri.NmraPacket;
004import jmri.PushbuttonPacket;
005import jmri.Turnout;
006import jmri.implementation.AbstractTurnout;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * Implement a Turnout via NCE communications.
012 * <p>
013 * This object doesn't listen to the NCE communications. This is because it
014 * should be the only object that is sending messages for this turnout; more
015 * than one Turnout object pointing to a single device is not allowed.
016 *
017 * @author Bob Jacobsen Copyright (C) 2001
018 * @author Daniel Boudreau (C) 2007
019 */
020public class NceTurnout extends AbstractTurnout {
021
022    NceTrafficController tc = null;
023    String prefix = "";
024
025    /**
026     * NCE turnouts use the NMRA number (0-2044) as their numerical
027     * identification.
028     * @param tc traffic controller for connection
029     * @param p system connection prefix
030     * @param i NMRA turnout number
031     */
032    public NceTurnout(NceTrafficController tc, String p, int i) {
033        super(p + "T" + i);
034        this.tc = tc;
035        this.prefix = p + "T";
036        _number = i;
037        if (_number < NmraPacket.accIdLowLimit || _number > NmraPacket.accIdHighLimit) {
038            throw new IllegalArgumentException("Turnout value: " + _number 
039                    + " not in the range " + NmraPacket.accIdLowLimit + " to " 
040                    + NmraPacket.accIdHighLimit);
041        }
042        // At construction, register for messages
043        initialize();
044    }
045
046    private synchronized void initialize() {
047        numNtTurnouts++; // increment the total number of NCE turnouts
048        // update feedback modes, MONITORING requires PowerPro system with new EPROM    
049        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006 && tc.getUsbSystem() == NceTrafficController.USB_SYSTEM_NONE) {
050            if (modeNames == null) {
051                if (_validFeedbackNames.length != _validFeedbackModes.length) {
052                    log.error("int and string feedback arrays different length");
053                }
054                modeNames = new String[_validFeedbackNames.length + 1];
055                modeValues = new int[_validFeedbackNames.length + 1];
056                for (int i = 0; i < _validFeedbackNames.length; i++) {
057                    modeNames[i] = _validFeedbackNames[i];
058                    modeValues[i] = _validFeedbackModes[i];
059                }
060                modeNames[_validFeedbackNames.length] = "MONITORING";
061                modeValues[_validFeedbackNames.length] = MONITORING;
062            }
063            _validFeedbackTypes |= MONITORING;
064            _validFeedbackNames = modeNames;
065            _validFeedbackModes = modeValues;
066        }
067        _enableCabLockout = true;
068        _enablePushButtonLockout = true;
069    }
070    static String[] modeNames = null;
071    static int[] modeValues = null;
072    private static int numNtTurnouts = 0;
073
074    public int getNumber() {
075        return _number;
076    }
077
078    public static int getNumNtTurnouts() {
079        return numNtTurnouts;
080    }
081
082    /**
083     * {@inheritDoc}
084     */
085    @Override
086    protected void forwardCommandChangeToLayout(int newState) {
087        // implementing classes will typically have a function/listener to get
088        // updates from the layout, which will then call
089        //  public void firePropertyChange(String propertyName,
090        //          Object oldValue,
091        //          Object newValue)
092        // _once_ if anything has changed state (or set the commanded state directly)
093
094        // sort out states
095        if ((newState & Turnout.CLOSED) != 0) {
096            // first look for the double case, which we can't handle
097            if ((newState & Turnout.THROWN) != 0) {
098                // this is the disaster case!
099                log.error("Cannot command both CLOSED and THROWN {}", newState);
100                return;
101            } else {
102                // send a CLOSED command
103                sendMessage(!getInverted());
104            }
105        } else {
106            // send a THROWN command
107            sendMessage(getInverted());
108        }
109    }
110
111    /**
112     * Send a message to the layout to lock or unlock the turnout pushbuttons if
113     * true, pushbutton lockout enabled
114     */
115    @Override
116    protected void turnoutPushbuttonLockout(boolean pushButtonLockout) {
117            log.debug("Send command to {} Pushbutton {}{}",
118                    pushButtonLockout ? "Lock" : "Unlock", prefix, _number);
119
120        byte[] bl = PushbuttonPacket.pushbuttonPkt(prefix, _number, pushButtonLockout);
121        NceMessage m = NceMessage.sendPacketMessage(tc, bl);
122        tc.sendNceMessage(m, null);
123    }
124
125    // data members
126    int _number;   // turnout number
127
128    /**
129     * Set the turnout known state to reflect what's been observed from the
130     * command station polling. A change there means that somebody commanded a
131     * state change (by using a throttle), and that command has
132     * already taken effect. Hence we use "newCommandedState" to indicate it's
133     * taken place. Must be followed by "newKnownState" to complete the turnout
134     * action.
135     *
136     * @param state Observed state, updated state from command station
137     */
138    synchronized void setCommandedStateFromCS(int state) {
139        if ((getFeedbackMode() != MONITORING)) {
140            return;
141        }
142
143        newCommandedState(state);
144    }
145
146    /**
147     * Set the turnout known state to reflect what's been observed from the
148     * command station polling. A change there means that somebody commanded a
149     * state change (by using a throttle), and that command has
150     * already taken effect. Hence we use "newKnownState" to indicate it's taken
151     * place.
152     *
153     * @param state Observed state, updated state from command station
154     */
155    synchronized void setKnownStateFromCS(int state) {
156        if ((getFeedbackMode() != MONITORING)) {
157            return;
158        }
159
160        newKnownState(state);
161    }
162
163    /**
164     * NCE turnouts can be inverted
165     */
166    @Override
167    public boolean canInvert() {
168        return true;
169    }
170    /**
171     * NCE turnouts can provide both modes when properly configured
172     *
173     * @return Both cab and pushbutton (decoder) modes
174     */
175    @Override
176    public int getPossibleLockModes() { return CABLOCKOUT | PUSHBUTTONLOCKOUT ; }
177
178    /**
179     * NCE turnouts support two types of lockouts, pushbutton and cab. Cab
180     * lockout requires the feedback mode to be Monitoring
181     */
182    @Override
183    public boolean canLock(int turnoutLockout) {
184        // can not lock if using a USB
185        if (tc.getUsbSystem() != NceTrafficController.USB_SYSTEM_NONE) {
186            return false;
187        }
188        // check to see if push button lock is enabled and valid decoder
189        String dn = getDecoderName();
190        if ((turnoutLockout & PUSHBUTTONLOCKOUT) != 0 && _enablePushButtonLockout
191                && dn != null && !dn.equals(PushbuttonPacket.unknown)) {
192            return true;
193        }
194        // check to see if cab lockout is enabled
195        if ((turnoutLockout & CABLOCKOUT) != 0
196                && getFeedbackMode() == MONITORING && _enableCabLockout) {
197            return true;
198        } else {
199            return false;
200        }
201    }
202
203    /**
204     * Control which turnout locks are enabled
205     */
206    @Override
207    public void enableLockOperation(int turnoutLockout, boolean enabled) {
208        if ((turnoutLockout & CABLOCKOUT) != 0) {
209            if (enabled) {
210                _enableCabLockout = true;
211            } else {
212                // unlock cab before disabling
213                _cabLockout = false;
214                _enableCabLockout = false;
215                // pushbutton lockout has to be enabled if cab lockout is disabled
216                _enablePushButtonLockout = true;
217            }
218        }
219        if ((turnoutLockout & PUSHBUTTONLOCKOUT) != 0) {
220            if (enabled) {
221                _enablePushButtonLockout = true;
222            } else {
223                // only time we can disable pushbuttons is if we can lockout cabs
224                if (getFeedbackMode() != MONITORING) {
225                    return;
226                }
227                // pushbutton lockout has to be enabled if cab lockout is disabled
228                if (_enableCabLockout) {
229                    _enablePushButtonLockout = false;
230                }
231            }
232        }
233    }
234
235    protected void sendMessage(boolean closed) {
236        // get the packet
237        // dBoudreau  Added support for new accessory binary command
238
239        if (tc.getCommandOptions() >= NceTrafficController.OPTION_2006) {
240
241            byte[] bl = NceBinaryCommand.accDecoder(_number, closed);
242
243            if (log.isDebugEnabled()) {
244                log.debug("Command: {} {} {} {} {}",
245                        Integer.toHexString(0xFF & bl[0]),
246                        Integer.toHexString(0xFF & bl[1]),
247                        Integer.toHexString(0xFF & bl[2]),
248                        Integer.toHexString(0xFF & bl[3]),
249                        Integer.toHexString(0xFF & bl[4]));
250            }
251
252            NceMessage m = NceMessage.createBinaryMessage(tc, bl);
253
254            tc.sendNceMessage(m, null);
255
256        } else {
257
258            byte[] bl = NmraPacket.accDecoderPkt(_number, closed);
259
260            if (log.isDebugEnabled()) {
261                log.debug("packet: {} {} {}",
262                        Integer.toHexString(0xFF & bl[0]),
263                        Integer.toHexString(0xFF & bl[1]),
264                        Integer.toHexString(0xFF & bl[2]));
265            }
266
267            NceMessage m = NceMessage.sendPacketMessage(tc, bl);
268
269            tc.sendNceMessage(m, null);
270        }
271    }
272
273    private final static Logger log = LoggerFactory.getLogger(NceTurnout.class);
274
275}