001package jmri.jmrix.lenz;
002
003import jmri.Consist;
004import jmri.ConsistListener;
005import jmri.DccLocoAddress;
006
007/**
008 * XNetConsist.java
009 *
010 * This is the Consist definition for a consist on an XPresNet system. it uses
011 * the XpressNet specific commands to build a consist.
012 *
013 * @author Paul Bender Copyright (C) 2004-2010
014 */
015public class XNetConsist extends jmri.implementation.DccConsist implements XNetListener {
016
017    // We need to wait for replies before completing consist
018    // operations
019    private static final int IDLESTATE = 0;
020    private static final int ADDREQUESTSENTSTATE = 1;
021    private static final int REMOVEREQUESTSENTSTATE = 2;
022
023    private int _state = IDLESTATE;
024
025    private DccLocoAddress _locoAddress = null; // address for the last request
026    private boolean _directionNormal = false; // direction of the last request
027
028    protected XNetTrafficController tc; // hold the traffic controller associated with this consist.
029
030    /**
031     * Initialize a consist for the specific address.
032     * Default consist type is an advanced consist.
033     * @param address loco address.
034     * @param controller system connection traffic controller.
035     * @param systemMemo system connection.
036     */
037    public XNetConsist(int address, XNetTrafficController controller, XNetSystemConnectionMemo systemMemo) {
038        super(address);
039        tc = controller;
040        this.systemMemo = systemMemo;
041        // At construction, register for messages
042        tc.addXNetListener(XNetInterface.COMMINFO
043                | XNetInterface.CONSIST,
044                XNetConsist.this);
045    }
046
047    /**
048     * Initialize a consist for the specific address.
049     * Default consist type is an advanced consist.
050     * @param address loco address.
051     * @param controller system connection traffic controller.
052     * @param systemMemo system connection.
053     */
054    public XNetConsist(DccLocoAddress address, XNetTrafficController controller, XNetSystemConnectionMemo systemMemo) {
055        super(address);
056        tc = controller;
057        this.systemMemo = systemMemo;
058        // At construction, register for messages
059        tc.addXNetListener(XNetInterface.COMMINFO
060                | XNetInterface.CONSIST,
061                XNetConsist.this);
062    }
063
064    final XNetSystemConnectionMemo systemMemo;
065
066    /**
067     * Clean Up local storage, and remove the XNetListener.
068     */
069    @Override
070    public synchronized void dispose() {
071        super.dispose();
072        tc.removeXNetListener(
073                XNetInterface.COMMINFO
074                | XNetInterface.CONSIST,
075                this);
076    }
077
078    /**
079     * Set the Consist Type.
080     *
081     * @param consistType An integer, should be either
082     *                     jmri.Consist.ADVANCED_CONSIST or
083     *                     jmri.Consist.CS_CONSIST.
084     */
085    @Override
086    public void setConsistType(int consistType) {
087        switch (consistType) {
088            case Consist.ADVANCED_CONSIST:
089            case Consist.CS_CONSIST:
090                this.consistType = consistType;
091                break;
092            default:
093                log.error("Consist Type Not Supported");
094                notifyConsistListeners(new DccLocoAddress(0, false), ConsistListener.NotImplemented);
095                break;
096        }
097    }
098
099    /**
100     * Is this address allowed?
101     * <p>
102     * On Lenz systems, All addresses but 0 can be used in a consist (Either and
103     * Advanced Consist or a Double Header).
104     * {@inheritDoc}
105     * @param address {@link jmri.DccLocoAddress DccLocoAddress} object to
106     *                check.
107     */
108    @Override
109    public boolean isAddressAllowed(DccLocoAddress address) {
110        return address.getNumber() != 0;
111    }
112
113    /**
114     * Is there a size limit for this consist?
115     *
116     * @return 2 For Lenz double headers. -1 (no limit) For Decoder Assisted
117     *         Consists. 0 for any other consist type.
118     */
119    @Override
120    public int sizeLimit() {
121        switch (consistType) {
122            case ADVANCED_CONSIST:
123                return -1;
124            case CS_CONSIST:
125                return 2;
126            default:
127                return 0;
128        }
129    }
130
131    /**
132     * Does the consist contain the specified address?
133     * {@inheritDoc}
134     * @param address {@link jmri.DccLocoAddress DccLocoAddress} object to
135     *                check.
136     */
137    @Override
138    public boolean contains(DccLocoAddress address) {
139        if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) {
140            return (consistList.contains(address));
141        } else {
142            log.error("Consist Type Not Supported");
143            notifyConsistListeners(address, ConsistListener.NotImplemented);
144        }
145        return false;
146    }
147
148    /**
149     * Get the relative direction setting for a specific locomotive in the
150     * consist.
151     *
152     * @param address {@link jmri.DccLocoAddress DccLocoAddress} object to check
153     * @return true means forward, false means backwards.
154     */
155    @Override
156    public boolean getLocoDirection(DccLocoAddress address) {
157        if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) {
158            return consistDir.get(address);
159        } else {
160            log.error("Consist Type Not Supported");
161            notifyConsistListeners(address, ConsistListener.NotImplemented);
162        }
163        return false;
164    }
165
166    /**
167     * Add an Address to the internal consist list object.
168     *
169     * @param locoAddress     {@link jmri.DccLocoAddress address} of the
170     *                        locomotive to add.
171     * @param directionNormal true for normal direction, false for reverse.
172     */
173    private synchronized void addToConsistList(DccLocoAddress locoAddress, boolean directionNormal) {
174        if (!(consistList.contains(locoAddress))) {
175            consistList.add(locoAddress);
176        }
177        consistDir.put(locoAddress, directionNormal);
178        if (consistType == CS_CONSIST && consistList.size() == 2) {
179            notifyConsistListeners(locoAddress,
180                    ConsistListener.OPERATION_SUCCESS
181                    | ConsistListener.CONSIST_FULL);
182        } else {
183            notifyConsistListeners(locoAddress,
184                    ConsistListener.OPERATION_SUCCESS);
185        }
186    }
187
188    /**
189     * Remove an address from the internal consist list object.
190     *
191     * @param locoAddress {@link jmri.DccLocoAddress address} of the locomotive
192     *                    to remove.
193     */
194    private synchronized void removeFromConsistList(DccLocoAddress locoAddress) {
195        if (consistList.contains(locoAddress)) {
196            consistDir.remove(locoAddress);
197            consistList.remove(locoAddress);
198        }
199        notifyConsistListeners(locoAddress, ConsistListener.OPERATION_SUCCESS);
200    }
201
202    /**
203     * Add a Locomotive to a Consist.
204     *
205     * @param locoAddress     the Locomotive address to add to the locomotive
206     * @param directionNormal is True if the locomotive is traveling the same
207     *                        direction as the consist, or false otherwise
208     */
209    @Override
210    public synchronized void add(DccLocoAddress locoAddress, boolean directionNormal) {
211        switch (consistType) {
212            case ADVANCED_CONSIST:
213                addToAdvancedConsist(locoAddress, directionNormal);
214                // save the address for the check after we get a response
215                // from the command station
216                _locoAddress = locoAddress;
217                _directionNormal = directionNormal;
218                break;
219            case CS_CONSIST:
220                if (consistList.size() < 2) {
221                    // Lenz Double Headers require exactly 2 locomotives, so
222                    // wait for the second locomotive to be added to start
223                    if (consistList.size() == 1 && !consistList.contains(locoAddress)) {
224                        addToCSConsist(locoAddress, directionNormal);
225                        // save the address for the check after we get a response
226                        // from the command station
227                        _locoAddress = locoAddress;
228                        _directionNormal = directionNormal;
229                    } else if (consistList.size() < 1) {
230                        // we're going to just add this directly, since we
231                        // can't form the consist yet.
232                        addToConsistList(locoAddress, directionNormal);
233                    } else {
234                        // we must have gotten here because we tried to add
235                        // a locomotive already in this consist.
236                        notifyConsistListeners(locoAddress,
237                                ConsistListener.CONSIST_ERROR
238                                | ConsistListener.ALREADY_CONSISTED);
239                    }
240                } else {
241                    // The only way it is valid for us to do something
242                    // here is if the locomotive we're adding is
243                    // already in the consist and we want to change
244                    // its direction
245                    if (consistList.size() == 2
246                            && consistList.contains(locoAddress)) {
247                        addToCSConsist(locoAddress, directionNormal);
248                        // save the address for the check after we get aresponse
249                        // from the command station
250                        _locoAddress = locoAddress;
251                        _directionNormal = directionNormal;
252                    } else {
253                        notifyConsistListeners(locoAddress,
254                                ConsistListener.CONSIST_ERROR
255                                | ConsistListener.CONSIST_FULL);
256                    }
257                }
258                break;
259            default:
260                log.error("Consist Type Not Supported");
261                notifyConsistListeners(locoAddress, ConsistListener.NotImplemented);
262                break;
263        }
264    }
265
266    /**
267     * Restore a Locomotive to an Advanced Consist, but don't write to the
268     * command station.
269     * <p>
270     * This is used for restoring the consist from a file or adding a consist
271     * read from the command station.
272     *
273     * @param locoAddress     the Locomotive address to add to the locomotive
274     * @param directionNormal True if the locomotive is traveling the same
275     *                        direction as the Consist, or false otherwise.
276     */
277    @Override
278    public synchronized void restore(DccLocoAddress locoAddress, boolean directionNormal) {
279        switch (consistType) {
280            case ADVANCED_CONSIST:
281            case CS_CONSIST:
282                addToConsistList(locoAddress, directionNormal);
283                break;
284            default:
285                log.error("Consist Type Not Supported");
286                notifyConsistListeners(locoAddress, ConsistListener.NotImplemented);
287                break;
288        }
289    }
290
291    /**
292     * Remove a Locomotive from this Consist.
293     *
294     * @param locoAddress the Locomotive address to add to the Consist
295     */
296    @Override
297    public synchronized void remove(DccLocoAddress locoAddress) {
298        log.debug("Consist {}: remove called for address {}", consistAddress, locoAddress);
299        switch (consistType) {
300            case ADVANCED_CONSIST:
301                // save the address for the check after we get a response
302                // from the command station
303                _locoAddress = locoAddress;
304                removeFromAdvancedConsist(locoAddress);
305                break;
306            case CS_CONSIST:
307                // Lenz Double Headers must be formed with EXACTLY 2
308                // addresses, so if there are two addresses in the list,
309                // we'll actually send the commands to remove the consist
310                if (consistList.size() == 2
311                        && _state != REMOVEREQUESTSENTSTATE) {
312                    // save the address for the check after we get a response
313                    // from the command station
314                    _locoAddress = locoAddress;
315                    removeFromCSConsist(locoAddress);
316                } else {
317                    // we just want to remove this from the list.
318                    if (_state != REMOVEREQUESTSENTSTATE
319                            || _locoAddress != locoAddress) {
320                        removeFromConsistList(locoAddress);
321                    }
322                }
323                break;
324            default:
325                log.error("Consist Type Not Supported");
326                notifyConsistListeners(locoAddress, ConsistListener.NotImplemented);
327                break;
328        }
329    }
330
331    /**
332     * Add a Locomotive to an Advanced Consist.
333     *
334     * @param locoAddress     the Locomotive address to add to the locomotive
335     * @param directionNormal is True if the locomotive is traveling the same
336     *                        direction as the consist, or false otherwise.
337     */
338    @Override
339    protected synchronized void addToAdvancedConsist(DccLocoAddress locoAddress, boolean directionNormal) {
340        log.debug("Adding locomotive {} to consist {}", locoAddress.getNumber(), consistAddress.getNumber());
341        // First, check to see if the locomotive is in the consist already
342        if (this.contains(locoAddress)) {
343            // we want to remove the locomotive from the consist
344            // before we re-add it. (we might just be switching
345            // the direction of the locomotive in the consist)
346            removeFromAdvancedConsist(locoAddress);
347        }
348        // set the speed of the locomotive to zero, to make sure we have
349        // control over it.
350        sendDirection(locoAddress, directionNormal);
351
352        // All we have to do here is create an apropriate XNetMessage,
353        // and send it.
354        XNetMessage msg = XNetMessage.getAddLocoToConsistMsg(consistAddress.getNumber(), 
355            locoAddress.getNumber(), directionNormal);
356        tc.sendXNetMessage(msg, this);
357        _state = ADDREQUESTSENTSTATE;
358    }
359
360    /**
361     * Remove a Locomotive from an Advanced Consist.
362     *
363     * @param locoAddress the Locomotive address to add to the locomotive
364     */
365    @Override
366    protected synchronized void removeFromAdvancedConsist(DccLocoAddress locoAddress) {
367        // set the speed of the locomotive to zero, to make sure we
368        // have control over it.
369        sendDirection(locoAddress, getLocoDirection(locoAddress));
370        // All we have to do here is create an apropriate XNetMessage,
371        // and send it.
372        XNetMessage msg = XNetMessage.getRemoveLocoFromConsistMsg(consistAddress.getNumber(), locoAddress.getNumber());
373        tc.sendXNetMessage(msg, this);
374        _state = REMOVEREQUESTSENTSTATE;
375    }
376
377    /**
378     * Add a Locomotive to a Lenz Double Header
379     *
380     * @param locoAddress     the Locomotive address to add to the locomotive
381     * @param directionNormal is True if the locomotive is traveling the same
382     *                        direction as the consist, or false otherwise.
383     */
384    private synchronized void addToCSConsist(DccLocoAddress locoAddress, boolean directionNormal) {
385
386        if (consistAddress.equals(locoAddress)) {
387            // Something went wrong here, we are trying to add a
388            // trailing locomotive to the consist with the same
389            // address as the lead locomotive.  This isn't supposed to
390            // happen.
391            log.error("Attempted to add {} to consist {}", locoAddress, consistAddress);
392            _state = IDLESTATE;
393            notifyConsistListeners(_locoAddress,
394                    ConsistListener.CONSIST_ERROR
395                    | ConsistListener.ALREADY_CONSISTED);
396            return;
397        }
398
399        // If the consist already contains the locomotive in
400        // question, we need to disolve the consist
401        if (consistList.size() == 2
402                && consistList.contains(locoAddress)) {
403            XNetMessage msg = XNetMessage.getDisolveDoubleHeaderMsg(
404                    consistList.get(0).getNumber());
405            tc.sendXNetMessage(msg, this);
406        }
407
408        // We need to set the speed and direction of both
409        // locomotives to establish control.
410        DccLocoAddress address = consistList.get(0);
411        Boolean direction = consistDir.get(address);
412        sendDirection(address, direction);
413        sendDirection(locoAddress, directionNormal);
414
415        // All we have to do here is create an apropriate XNetMessage,
416        // and send it.
417        XNetMessage msg = XNetMessage.getBuildDoubleHeaderMsg(address.getNumber(), locoAddress.getNumber());
418        tc.sendXNetMessage(msg, this);
419        _state = ADDREQUESTSENTSTATE;
420
421    }
422
423    /**
424     * Remove a Locomotive from a Lenz Double Header.locoAddress
425     * @param locoAddress is the Locomotive address, unused here.
426     */
427    public synchronized void removeFromCSConsist(DccLocoAddress locoAddress) {
428        // All we have to do here is create an apropriate XNetMessage,
429        // and send it.
430        XNetMessage msg = XNetMessage.getDisolveDoubleHeaderMsg(consistList.get(0).getNumber());
431        tc.sendXNetMessage(msg, this);
432        _state = REMOVEREQUESTSENTSTATE;
433    }
434
435    /**
436     * Listeners for messages from the command station.
437     */
438    @Override
439    public synchronized void message(XNetReply l) {
440        if (_state != IDLESTATE) {
441            // we're waiting for a reply, so examine what we received
442            if (l.isOkMessage()) {
443                if (_state == ADDREQUESTSENTSTATE) {
444                    addToConsistList(_locoAddress, _directionNormal);
445                    if (consistType == ADVANCED_CONSIST) {
446                       //set the value in the roster entry for CV19
447                       setRosterEntryCVValue(_locoAddress);
448                    }
449                } else if (_state == REMOVEREQUESTSENTSTATE) {
450                    if (consistType == ADVANCED_CONSIST) {
451                       //reset the value in the roster entry for CV19
452                       resetRosterEntryCVValue(_locoAddress);
453                    }
454                    removeFromConsistList(_locoAddress);
455                }
456                _state = IDLESTATE;
457            } else if (l.getElement(0) == XNetConstants.LOCO_MU_DH_ERROR) {
458                String text;
459                switch (l.getElement(1)) {
460                    case 0x81:
461                        text = "Selected Locomotive has not been operated by this XpressNet device or address 0 selected";
462                        _state = IDLESTATE;
463                        notifyConsistListeners(_locoAddress,
464                                ConsistListener.CONSIST_ERROR
465                                | ConsistListener.LOCO_NOT_OPERATED);
466                        break;
467                    case 0x82:
468                        text = "Selected Locomotive is being operated by another XpressNet device";
469                        _state = IDLESTATE;
470                        notifyConsistListeners(_locoAddress,
471                                ConsistListener.CONSIST_ERROR
472                                | ConsistListener.LOCO_NOT_OPERATED);
473                        break;
474                    case 0x83:
475                        text = "Selected Locomotive already in MU or DH";
476                        _state = IDLESTATE;
477                        notifyConsistListeners(_locoAddress,
478                                ConsistListener.CONSIST_ERROR
479                                | ConsistListener.ALREADY_CONSISTED);
480                        break;
481                    case 0x84:
482                        text = "Unit selected for MU or DH has speed setting other than 0";
483                        _state = IDLESTATE;
484                        notifyConsistListeners(_locoAddress,
485                                ConsistListener.CONSIST_ERROR
486                                | ConsistListener.NONZERO_SPEED);
487                        break;
488                    case 0x85:
489                        text = "Locomotive not in a MU";
490                        _state = IDLESTATE;
491                        notifyConsistListeners(_locoAddress,
492                                ConsistListener.CONSIST_ERROR
493                                | ConsistListener.NOT_CONSISTED);
494                        break;
495                    case 0x86:
496                        text = "Locomotive address not a multi-unit base address";
497                        _state = IDLESTATE;
498                        notifyConsistListeners(_locoAddress,
499                                ConsistListener.CONSIST_ERROR
500                                | ConsistListener.NOT_CONSIST_ADDR);
501
502                        break;
503                    case 0x87:
504                        text = "It is not possible to delete the locomotive";
505                        _state = IDLESTATE;
506                        notifyConsistListeners(_locoAddress,
507                                ConsistListener.CONSIST_ERROR
508                                | ConsistListener.DELETE_ERROR);
509                        break;
510                    case 0x88:
511                        text = "The Command Station Stack is Full";
512                        _state = IDLESTATE;
513                        notifyConsistListeners(_locoAddress,
514                                ConsistListener.CONSIST_ERROR
515                                | ConsistListener.STACK_FULL);
516                        break;
517                    default:
518                        text = "Unknown";
519                        _state = IDLESTATE;
520                        notifyConsistListeners(_locoAddress,
521                                ConsistListener.CONSIST_ERROR);
522                }
523                log.error("XpressNet MU+DH error: {}",text);
524            }
525        }
526    }
527
528    @Override
529    public void message(XNetMessage l) {
530    }
531
532   // Handle a timeout notification
533    @Override
534    public void notifyTimeout(XNetMessage msg) {
535        log.debug("Notified of timeout on message{}", msg);
536    }
537
538    /**
539     * Set the speed and direction of a locomotive; bypassing the commands in
540     * the throttle, since they don't work for this application.
541     * <p>
542     * For this application, we also set the speed setting to 0, which also
543     * establishes control over the locomotive in the consist.
544     *
545     * @param address   the DccLocoAddress of the locomotive.
546     * @param isForward the boolean value representing the desired direction
547     */
548    private void sendDirection(DccLocoAddress address, boolean isForward) {
549        XNetMessage msg = XNetMessage.getSpeedAndDirectionMsg(address.getNumber(),
550                jmri.SpeedStepMode.NMRA_DCC_28,
551                (float) 0.0,
552                isForward);
553        // now, we send the message to the command station
554        tc.sendXNetMessage(msg, this);
555    }
556
557    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetConsist.class);
558
559}