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