001/**
002 * LocoNetConsist.java
003 *
004 * This is the Consist definition for a consist on a LocoNet system. it uses the
005 * LocoNet specific commands to build a consist.
006 *
007 * @author Paul Bender Copyright (C) 2011
008 */
009package jmri.jmrix.loconet;
010
011import java.util.ArrayList;
012import jmri.Consist;
013import jmri.ConsistListener;
014import jmri.LocoAddress;
015import jmri.DccLocoAddress;
016import jmri.ThrottleListener;
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020public class LocoNetConsist extends jmri.implementation.DccConsist implements SlotListener, ThrottleListener {
021
022    private SlotManager slotManager = null;
023    private LnTrafficController trafficController = null;
024    private jmri.jmrix.AbstractThrottleManager throttleManager = null;
025    private LocoNetSlot leadSlot = null;
026
027    private ArrayList<DccLocoAddress> needToWrite = null;
028
029    // State Machine states
030    final static int IDLESTATE = 0;
031    final static int LEADREQUESTSTATE = 1;
032    final static int LINKSTAGEONESTATE = 2;
033    final static int LINKSTAGETWOSTATE = 4;
034    final static int LINKSTAGETHREESTATE = 8;
035    final static int UNLINKSTAGEONESTATE = 16;
036
037    private int consistRequestState = IDLESTATE;
038
039    // Initialize a consist for the specific address
040    // the Default consist type for LocoNet is a Command
041    // Station Consist.
042    public LocoNetConsist(int address, LocoNetSystemConnectionMemo lm) {
043        super(address);
044        this.slotManager = lm.getSlotManager();
045        this.trafficController = lm.getLnTrafficController();
046        this.throttleManager = (jmri.jmrix.AbstractThrottleManager) lm.getThrottleManager();
047        consistRequestState = LEADREQUESTSTATE;
048        consistType = Consist.CS_CONSIST;
049        needToWrite = new ArrayList<DccLocoAddress>();
050        throttleManager.requestThrottle(consistAddress, this, false);
051    }
052
053    // Initialize a consist for the specific address
054    // the Default consist type for LocoNet is a Command
055    // Station Consist.
056    public LocoNetConsist(DccLocoAddress address, LocoNetSystemConnectionMemo lm) {
057        super(address);
058        this.slotManager = lm.getSlotManager();
059        this.trafficController = lm.getLnTrafficController();
060        this.throttleManager = (jmri.jmrix.AbstractThrottleManager) lm.getThrottleManager();
061        consistRequestState = LEADREQUESTSTATE;
062        consistType = Consist.CS_CONSIST;
063        needToWrite = new ArrayList<DccLocoAddress>();
064        throttleManager.requestThrottle(consistAddress, this, false);
065    }
066
067    // Set the Consist Type
068    @Override
069    public void setConsistType(int consist_type) {
070        if (consist_type == Consist.ADVANCED_CONSIST) {
071            consistType = consist_type;
072            return;
073        } else if (consist_type == Consist.CS_CONSIST) {
074            consistType = consist_type;
075        } else {
076            log.error("Consist Type Not Supported");
077            notifyConsistListeners(new DccLocoAddress(0, false), ConsistListener.NotImplemented);
078        }
079    }
080
081    /* is this address allowed?
082     * On LocoNet systems, All addresses can be used in a Universal Consist
083     * and only 0 is not allowed in Advanced Consists.
084     */
085    @Override
086    public boolean isAddressAllowed(DccLocoAddress address) {
087        if (consistType == Consist.CS_CONSIST) {
088            return true;
089        } else if (address.getNumber() != 0) {
090            return (true);
091        } else {
092            return (false);
093        }
094    }
095
096    /* is there a size limit for this consist?
097     * For LocoNet returns -1 (no limit) for
098     * both CS and Advanced Consists
099     * return 0 for any other consist type.
100     */
101    @Override
102    public int sizeLimit() {
103        if (consistType == ADVANCED_CONSIST) {
104            return -1;
105        } else if (consistType == CS_CONSIST) {
106            return -1;
107        } else {
108            return 0;
109        }
110    }
111
112    // does the consist contain the specified address?
113    @Override
114    public boolean contains(DccLocoAddress address) {
115        if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) {
116            return consistList.contains(address);
117        } else {
118            log.error("Consist Type Not Supported");
119            notifyConsistListeners(address, ConsistListener.NotImplemented);
120        }
121        return false;
122    }
123
124    // get the relative direction setting for a specific
125    // locomotive in the consist
126    @Override
127    public boolean getLocoDirection(DccLocoAddress address) {
128        log.debug("consist {} obtaining direction for {} Consist List Size {}", consistAddress, address, consistList.size());
129        if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) {
130            if (address == consistAddress) {
131                return true;
132            }
133            if (consistList.contains(address)) {
134                Boolean Direction = consistDir.get(address);
135                return (Direction.booleanValue());
136            } else {
137                return (true);
138            }
139        } else {
140            log.error("Consist Type Not Supported");
141            notifyConsistListeners(address, ConsistListener.NotImplemented);
142        }
143        return false;
144    }
145
146    /*
147     * Add an Address to the internal Consist list object.
148     */
149    private synchronized void addToConsistList(DccLocoAddress LocoAddress, boolean directionNormal) {
150        Boolean Direction = Boolean.valueOf(directionNormal);
151        if (!(consistList.contains(LocoAddress))) {
152            consistList.add(LocoAddress);
153        }
154        if (consistDir.containsKey(LocoAddress)) {
155            consistDir.remove(LocoAddress);
156        }
157        consistDir.put(LocoAddress, Direction);
158    }
159
160    /*
161     * Remove an address from the internal Consist list object.
162     */
163    private synchronized void removeFromConsistList(DccLocoAddress LocoAddress) {
164        consistDir.remove(LocoAddress);
165        consistList.remove(LocoAddress);
166    }
167
168    /*
169     * Add a Locomotive to a Consist
170     *
171     * @param address         the Locomotive address to add to the locomotive
172     * @param directionNormal if the locomotive is traveling
173     *        the same direction as the consist, false otherwise
174     */
175    @Override
176    public synchronized void add(DccLocoAddress LocoAddress, boolean directionNormal) {
177        if (LocoAddress == consistAddress) {
178            // this is required for command station consists on LocoNet.
179            addToConsistList(LocoAddress, directionNormal);
180            notifyConsistListeners(LocoAddress, ConsistListener.OPERATION_SUCCESS);
181        } else if (consistType == ADVANCED_CONSIST) {
182            if (consistList.contains(LocoAddress)) {
183                // we are changing the direction, so remove first,
184                // then add
185                removeFromAdvancedConsist(LocoAddress);
186            }
187            addToConsistList(LocoAddress, directionNormal);
188            if (leadSlot == null || consistRequestState != IDLESTATE) {
189                needToWrite.add(LocoAddress);
190            } else {
191                addToAdvancedConsist(LocoAddress, directionNormal);
192            }
193        } else if (consistType == CS_CONSIST) {
194            if (consistList.contains(LocoAddress)) {
195                // we are changing the direction, so remove first,
196                // then add
197                removeFromCSConsist(LocoAddress);
198            }
199            addToConsistList(LocoAddress, directionNormal);
200            if (leadSlot == null || consistRequestState != IDLESTATE) {
201                needToWrite.add(LocoAddress);
202            } else {
203                addToCSConsist(LocoAddress, directionNormal);
204            }
205        } else {
206            log.error("Consist Type Not Supported");
207            notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented);
208        }
209    }
210
211    private synchronized void delayedAdd() {
212        DccLocoAddress LocoAddress = needToWrite.get(0);
213        if (consistType == ADVANCED_CONSIST) {
214            addToAdvancedConsist(LocoAddress, getLocoDirection(LocoAddress));
215        } else if (consistType == CS_CONSIST) {
216            addToCSConsist(LocoAddress, getLocoDirection(LocoAddress));
217        }
218        needToWrite.remove(LocoAddress);
219    }
220
221    /*
222     * Restore a Locomotive to a Consist, but don't write to
223     * the command station.  This is used for restoring the consist
224     * from a file or adding a consist read from the command station.
225     *
226     * @param address         the Locomotive address to add to the locomotive
227     * @param directionNormal True if the locomotive is traveling
228     *        the same direction as the consist, false otherwise
229     */
230    @Override
231    public synchronized void restore(DccLocoAddress LocoAddress, boolean directionNormal) {
232        if (consistType == ADVANCED_CONSIST) {
233            addToConsistList(LocoAddress, directionNormal);
234        } else if (consistType == CS_CONSIST) {
235            addToConsistList(LocoAddress, directionNormal);
236        } else {
237            log.error("Consist Type Not Supported");
238            notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented);
239        }
240    }
241
242    /*
243     *  Remove a Locomotive from this Consist.
244     *
245     *  @param address is the Locomotive address to add to the locomotive
246     */
247    @Override
248    public synchronized void remove(DccLocoAddress LocoAddress) {
249        if (consistType == ADVANCED_CONSIST) {
250            removeFromAdvancedConsist(LocoAddress);
251            removeFromConsistList(LocoAddress);
252        } else if (consistType == CS_CONSIST) {
253            removeFromCSConsist(LocoAddress);
254            removeFromConsistList(LocoAddress);
255        } else {
256            log.error("Consist Type Not Supported");
257            notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented);
258        }
259    }
260
261    /*
262     *  Add a Locomotive to an Advanced Consist.
263     *
264     *  @param address         the Locomotive address to add to the locomotive
265     *  @param directionNormal True if the locomotive is traveling
266     *        the same direction as the consist, false otherwise
267     */
268    @Override
269    protected synchronized void addToAdvancedConsist(DccLocoAddress LocoAddress, boolean directionNormal) {
270        if (log.isDebugEnabled()) {
271            log.debug("Add Locomotive {} to advanced consist {} With Direction Normal {}.", LocoAddress.toString(), consistAddress.toString(), directionNormal);
272        }
273        //set the value in the roster entry for CV19
274        setRosterEntryCVValue(LocoAddress);
275        consistRequestState = LINKSTAGEONESTATE;
276        throttleManager.requestThrottle(LocoAddress, this, false);
277    }
278
279    /*
280     *  Remove a Locomotive from an Advanced Consist
281     *  @param address is the Locomotive address to add to the locomotive
282     */
283    @Override
284    protected synchronized void removeFromAdvancedConsist(DccLocoAddress LocoAddress) {
285        if (log.isDebugEnabled()) {
286            log.debug(" Remove Locomotive {} from advanced consist {}", LocoAddress.toString(), consistAddress.toString());
287        }
288        //reset the value in the roster entry for CV19
289        resetRosterEntryCVValue(LocoAddress);
290        slotManager.slotFromLocoAddress(LocoAddress.getNumber(), this);
291        consistRequestState = UNLINKSTAGEONESTATE;
292    }
293
294    /*
295     *  Add a Locomotive to a LocoNet Universal Consist.
296     *  @param address is the Locomotive address to add to the locomotive
297     *  @param directionNormal is True if the locomotive is traveling
298     *        the same direction as the consist, or false otherwise.
299     */
300    private synchronized void addToCSConsist(DccLocoAddress LocoAddress, boolean directionNormal) {
301        if (log.isDebugEnabled()) {
302            log.debug("Add Locomotive {} to Standard Consist {} With Direction Normal {}.", LocoAddress.toString(), consistAddress.toString(), directionNormal);
303        }
304        if(consistList.size()<=1 && LocoAddress.equals(consistAddress)){
305          // there is only one address in this consist, no reason to link.
306          notifyConsistListeners(LocoAddress,ConsistListener.OPERATION_SUCCESS);
307          return;
308        }
309        throttleManager.requestThrottle(LocoAddress, this, false);
310        // skip right to stage 2, we do not need to status edit.
311        consistRequestState = LINKSTAGETWOSTATE;
312    }
313
314    /*
315     *  Remove a Locomotive from a LocoNet Universal Consist.
316     *  @param address is the Locomotive address to add to the locomotive
317     */
318    public synchronized void removeFromCSConsist(DccLocoAddress LocoAddress) {
319        if (log.isDebugEnabled()) {
320            log.debug("Remove Locomotive {} from Standard Consist {}.", LocoAddress.toString(), consistAddress.toString());
321        }
322        if(consistList.size()==1 && LocoAddress.equals(consistAddress)){
323          // there is only one address in this consist, no reason to link.
324          notifyConsistListeners(LocoAddress,ConsistListener.OPERATION_SUCCESS);
325          return;
326        }
327        slotManager.slotFromLocoAddress(LocoAddress.getNumber(), this);
328        consistRequestState = UNLINKSTAGEONESTATE;
329    }
330
331    /*
332     * create and send a message to link two slots
333     * @param lead is the slot which is the leader
334     * @param follow is the slot which will follow the leader
335     */
336    private void linkSlots(LocoNetSlot lead, LocoNetSlot follow) {
337        LocoNetMessage msg;
338        if (lead != follow) {
339            if (slotManager.getLoconetProtocol() == LnConstants.LOCONETPROTOCOL_TWO) {
340                msg = new LocoNetMessage(6);
341                int dest1 = follow.getSlot() / 128;
342                int dest2 = follow.getSlot() % 128;
343                int src1 = lead.getSlot() / 128;
344                int src2 = lead.getSlot() % 128;
345                msg.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL);
346                msg.setElement(1, dest1 | 0b00111000);
347                msg.setElement(2, dest2 & 0x7F);
348                msg.setElement(3, src1  | 0b01000000);
349                msg.setElement(4, src2 & 0x7F);
350            } else {
351                msg = new LocoNetMessage(4);
352                msg.setOpCode(LnConstants.OPC_LINK_SLOTS);
353                msg.setElement(1, follow.getSlot());
354                msg.setElement(2, lead.getSlot());
355            }
356            trafficController.sendLocoNetMessage(msg);
357        } else {
358          // lead == follow
359          // this is an error, notify the consist listeners.
360          follow.removeSlotListener(this);
361          notifyConsistListeners(new DccLocoAddress(follow.locoAddr(),
362                throttleManager.canBeLongAddress(follow.locoAddr())),
363                ConsistListener.CONSIST_ERROR);
364        }
365        consistRequestState = IDLESTATE;
366        if (needToWrite.size() != 0) {
367            delayedAdd();
368        }
369    }
370
371    /*
372     * create and send a message to unlink two slots
373     * @param lead is the slot which is the leader
374     * @param follow is the slot which was following the leader
375     */
376    private void unlinkSlots(LocoNetSlot lead, LocoNetSlot follow) {
377        LocoNetMessage msg;
378        if (lead != follow) {
379            if (slotManager.getLoconetProtocol() == LnConstants.LOCONETPROTOCOL_TWO) {
380                msg = new LocoNetMessage(6);
381                int src1 = lead.getSlot() / 128;
382                int src2 = lead.getSlot() % 128;
383                int dest1 = follow.getSlot() / 128;
384                int dest2 = follow.getSlot() % 128;
385                msg.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL);
386                msg.setElement(3, src1 | 0b01010000);
387                msg.setElement(4, src2 & 0x7F);
388                msg.setElement(1, dest1 | 0b00111000);
389                msg.setElement(2, dest2 & 0x7F);
390            } else {
391                msg = new LocoNetMessage(4);
392                msg.setOpCode(LnConstants.OPC_UNLINK_SLOTS);
393                msg.setElement(1, follow.getSlot());
394                msg.setElement(2, lead.getSlot());
395            }
396            trafficController.sendLocoNetMessage(msg);
397        } else {
398          // lead == follow
399          // this is an error, notify the consist listeners.
400          follow.removeSlotListener(this);
401          notifyConsistListeners(new DccLocoAddress(follow.locoAddr(),
402                throttleManager.canBeLongAddress(follow.locoAddr())),
403                ConsistListener.CONSIST_ERROR | ConsistListener.DELETE_ERROR );
404        }
405        consistRequestState = IDLESTATE;
406        if (needToWrite.size() != 0) {
407            delayedAdd();
408        }
409    }
410
411    private void setDirection(LocoNetThrottle t) {
412        log.debug("consist {} set direction for {}", consistAddress, t.getLocoAddress());
413        // send a command to set the direction
414        // of the locomotive in the slot.
415        Boolean directionNormal = getLocoDirection((DccLocoAddress) t.getLocoAddress());
416        if (directionNormal) {
417            t.setIsForward(leadSlot.isForward());
418        } else {
419            t.setIsForward(!leadSlot.isForward());
420        }
421
422        consistRequestState = LINKSTAGETWOSTATE;
423    }
424
425    private void setSlotModeAdvanced(LocoNetSlot s) {
426        // set the slot so that it can be an advanced consist
427        int oldstatus = s.slotStatus();
428        int newstatus = oldstatus | LnConstants.STAT1_SL_SPDEX;
429        trafficController.sendLocoNetMessage(s.writeStatus(newstatus));
430    }
431
432    // slot listener interface functions
433    @Override
434    public void notifyChangedSlot(LocoNetSlot s) {
435        log.debug("Notified slot {} changed with mode {} slot consist state: {}", s.getSlot(), consistRequestState, LnConstants.CONSIST_STAT(s.consistStatus()));
436        switch (consistRequestState) {
437            case LEADREQUESTSTATE:
438                leadSlot = s;
439                consistRequestState = IDLESTATE;
440                break;
441            case LINKSTAGEONESTATE:
442                s.addSlotListener(this);
443                setSlotModeAdvanced(s);
444                consistRequestState = LINKSTAGETWOSTATE;
445                break;
446            case LINKSTAGETWOSTATE:
447                linkSlots(leadSlot, s);
448                break;
449            case UNLINKSTAGEONESTATE:
450                unlinkSlots(leadSlot, s);
451                break;
452            default:
453                s.removeSlotListener(this);
454                notifyConsistListeners(new DccLocoAddress(s.locoAddr(),
455                        throttleManager.canBeLongAddress(s.locoAddr())),
456                        ConsistListener.OPERATION_SUCCESS);
457                if (needToWrite.size() != 0) {
458                    delayedAdd();
459                } else {
460                    consistRequestState = IDLESTATE;
461                }
462        }
463    }
464
465    // Throttle listener interface functions
466    @Override
467    public void notifyThrottleFound(jmri.DccThrottle t) {
468        log.debug("notified Throttle {} found with mode {}", t.getLocoAddress(), consistRequestState);
469        try {
470            if (consistRequestState == LEADREQUESTSTATE) {
471                ((LocoNetThrottle) t).setIsForward(true);
472                leadSlot = ((LocoNetThrottle) t).getLocoNetSlot();
473                consistRequestState = IDLESTATE;
474                if (needToWrite.size() != 0) {
475                    delayedAdd();
476                }
477            } else {
478                LocoNetSlot tempSlot = ((LocoNetThrottle) t).getLocoNetSlot();
479                if (tempSlot != null) {
480                    tempSlot.addSlotListener(this);
481                    if (consistRequestState == LINKSTAGEONESTATE) {
482                        notifyChangedSlot(tempSlot);
483                        setDirection(((LocoNetThrottle) t));
484                        consistRequestState = LINKSTAGETWOSTATE;
485                    } else {
486                        setDirection(((LocoNetThrottle) t));
487                    }
488                } else {
489                    log.error("Cannot notify a throttle's slot if the slot is null!");
490                }
491            }
492        } catch (java.lang.ClassCastException cce) {
493            // if the simulator is in use, we will
494            // get a ClassCastException.
495            if (consistRequestState == LEADREQUESTSTATE) {
496                t.setIsForward(true);
497                consistRequestState = IDLESTATE;
498                if (needToWrite.size() != 0) {
499                    delayedAdd();
500                }
501            } else {
502                if (t instanceof LocoNetThrottle) {
503                    LocoNetThrottle lt = (LocoNetThrottle)t;
504                    setDirection(lt);
505                }
506            }
507        }
508    }
509
510    @Override
511    public void notifyFailedThrottleRequest(LocoAddress address, String reason) {
512        if (! (address instanceof DccLocoAddress)) {
513            throw new IllegalArgumentException("address is not a DccLocoAddress object");
514        }
515        notifyConsistListeners((DccLocoAddress) address,
516                ConsistListener.CONSIST_ERROR);
517        removeFromConsistList((DccLocoAddress) address);
518        consistRequestState = IDLESTATE;
519    }
520
521    /**
522     * No steal or share decisions made locally
523     * <p>
524     * {@inheritDoc}
525     */
526    @Override
527    public void notifyDecisionRequired(jmri.LocoAddress address, DecisionType question) {
528    }
529
530    private final static Logger log = LoggerFactory.getLogger(LocoNetConsist.class);
531
532}