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