001package jmri.jmrix.easydcc;
002
003import jmri.Consist;
004import jmri.ConsistListener;
005import jmri.DccLocoAddress;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009/**
010 * This is the Consist definition for a consist on an EasyDCC system. it uses
011 * the EasyDcc specific commands to build a consist.
012 *
013 * @author Paul Bender Copyright (C) 2006
014 */
015public class EasyDccConsist extends jmri.implementation.DccConsist implements EasyDccListener {
016
017    private EasyDccSystemConnectionMemo _memo = null;
018
019    // Initialize a consist for the specific address.
020    // The Default consist type is an advanced consist
021    public EasyDccConsist(int address, EasyDccSystemConnectionMemo memo) {
022        super(address);
023        _memo = memo;
024    }
025
026    // Initialize a consist for the specific address.
027    // The Default consist type is an advanced consist
028    public EasyDccConsist(DccLocoAddress address, EasyDccSystemConnectionMemo memo) {
029        super(address);
030        _memo = memo;
031    }
032
033    // Clean Up local storage.
034    @Override
035    public void dispose() {
036        super.dispose();
037    }
038
039    // Set the Consist Type.
040    @Override
041    public void setConsistType(int consist_type) {
042        if (consist_type == Consist.ADVANCED_CONSIST) {
043            consistType = consist_type;
044            return;
045        } else if (consist_type == Consist.CS_CONSIST) {
046            consistType = consist_type;
047        } else {
048            log.error("Consist Type Not Supported");
049            notifyConsistListeners(new DccLocoAddress(0, false), ConsistListener.NotImplemented);
050        }
051    }
052
053    /**
054     * Is this address allowed?
055     * On EasyDCC systems, all addresses but 0 can be used in a consist
056     * (either an Advanced Consist or a Standard Consist).
057     */
058    @Override
059    public boolean isAddressAllowed(DccLocoAddress address) {
060        if (address.getNumber() != 0) {
061            return (true);
062        } else {
063            return (false);
064        }
065    }
066
067    /**
068     * Is there a size limit for this consist?
069     *
070     * @return 8 for EasyDcc Standard Consist,
071     * -1 for Decoder Assisted Consists (no limit),
072     * 0 for any other consist type
073     */
074    @Override
075    public int sizeLimit() {
076        if (consistType == ADVANCED_CONSIST) {
077            return -1;
078        } else if (consistType == CS_CONSIST) {
079            return 8;
080        } else {
081            return 0;
082        }
083    }
084
085    /**
086     * Does the consist contain the specified address?
087     */
088    @Override
089    public boolean contains(DccLocoAddress address) {
090        if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) {
091            return consistList.contains(address);
092        } else {
093            log.error("Consist Type Not Supported");
094            notifyConsistListeners(address, ConsistListener.NotImplemented);
095        }
096        return false;
097    }
098
099    /**
100     * Get the relative direction setting for a specific
101     * locomotive in the consist.
102     */
103    @Override
104    public boolean getLocoDirection(DccLocoAddress address) {
105        if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) {
106            Boolean Direction = consistDir.get(address);
107            return (Direction.booleanValue());
108        } else {
109            log.error("Consist Type Not Supported");
110            notifyConsistListeners(address, ConsistListener.NotImplemented);
111        }
112        return false;
113    }
114
115    /**
116     * Add an Address to the internal consist list object.
117     */
118    private synchronized void addToConsistList(DccLocoAddress LocoAddress, boolean directionNormal) {
119        Boolean Direction = Boolean.valueOf(directionNormal);
120        if (!(consistList.contains(LocoAddress))) {
121            consistList.add(LocoAddress);
122        }
123        consistDir.put(LocoAddress, Direction);
124        if (consistType == CS_CONSIST && consistList.size() == 8) {
125            notifyConsistListeners(LocoAddress,
126                    ConsistListener.OPERATION_SUCCESS
127                    | ConsistListener.CONSIST_FULL);
128        } else {
129            notifyConsistListeners(LocoAddress,
130                    ConsistListener.OPERATION_SUCCESS);
131        }
132    }
133
134    /**
135     * Remove an address from the internal consist list object.
136     */
137    private synchronized void removeFromConsistList(DccLocoAddress LocoAddress) {
138        consistDir.remove(LocoAddress);
139        consistList.remove(LocoAddress);
140        notifyConsistListeners(LocoAddress, ConsistListener.OPERATION_SUCCESS);
141    }
142
143    /**
144     * Add a Locomotive to a Consist.
145     *
146     * @param LocoAddress is the Locomotive address to add to the locomotive
147     * @param directionNormal is True if the locomotive is traveling
148     *        the same direction as the consist, or false otherwise.
149     */
150    @Override
151    public synchronized void add(DccLocoAddress LocoAddress, boolean directionNormal) {
152        if (consistType == ADVANCED_CONSIST) {
153            addToConsistList(LocoAddress, directionNormal);
154            addToAdvancedConsist(LocoAddress, directionNormal);
155            //set the value in the roster entry for CV19
156            setRosterEntryCVValue(LocoAddress);
157        } else if (consistType == CS_CONSIST) {
158            if (consistList.size() < 8) {
159                addToConsistList(LocoAddress, directionNormal);
160                addToCSConsist(LocoAddress, directionNormal);
161            } else {
162                notifyConsistListeners(LocoAddress,
163                        ConsistListener.CONSIST_ERROR
164                        | ConsistListener.CONSIST_FULL);
165            }
166        } else {
167            log.error("Consist Type Not Supported");
168            notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented);
169        }
170    }
171
172    /**
173     * Restore a Locomotive to an Advanced Consist, but don't write to
174     * the command station.  This is used for restoring the consist
175     * from a file or adding a consist read from the command station.
176     *
177     * @param LocoAddress is the Locomotive address to add to the locomotive
178     * @param directionNormal is True if the locomotive is traveling
179     *        the same direction as the consist, or false otherwise.
180     */
181    @Override
182    public synchronized void restore(DccLocoAddress LocoAddress, boolean directionNormal) {
183        if (consistType == ADVANCED_CONSIST) {
184            addToConsistList(LocoAddress, directionNormal);
185        } else if (consistType == CS_CONSIST) {
186            addToConsistList(LocoAddress, directionNormal);
187        } else {
188            log.error("Consist Type Not Supported");
189            notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented);
190        }
191    }
192
193    /**
194     * Remove a Locomotive from this Consist.
195     *
196     * @param LocoAddress is the Locomotive address to add to the locomotive
197     */
198    @Override
199    public synchronized void remove(DccLocoAddress LocoAddress) {
200        if (consistType == ADVANCED_CONSIST) {
201            //reset the value in the roster entry for CV19
202            resetRosterEntryCVValue(LocoAddress);
203            removeFromAdvancedConsist(LocoAddress);
204            removeFromConsistList(LocoAddress);
205        } else if (consistType == CS_CONSIST) {
206            removeFromCSConsist(LocoAddress);
207            removeFromConsistList(LocoAddress);
208        } else {
209            log.error("Consist Type Not Supported");
210            notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented);
211        }
212    }
213
214    /**
215     * Add a Locomotive to an Advanced Consist.
216     *
217     * @param LocoAddress is the Locomotive address to add to the locomotive
218     * @param directionNormal is True if the locomotive is traveling
219     *        the same direction as the consist, or false otherwise.
220     */
221    @Override
222    protected synchronized void addToAdvancedConsist(DccLocoAddress LocoAddress, boolean directionNormal) {
223        if (log.isDebugEnabled()) {
224            log.debug("Add Locomotive {} to advanced consist {} With Direction Normal {}.", LocoAddress.toString(), consistAddress.toString(), directionNormal);
225        }
226        // create the message and fill it,
227        byte[] contents = jmri.NmraPacket.consistControl(LocoAddress.getNumber(),
228                LocoAddress.isLongAddress(),
229                consistAddress.getNumber(),
230                directionNormal);
231        EasyDccMessage msg = new EasyDccMessage(4 + 3 * contents.length);
232        msg.setOpCode('S');
233        msg.setElement(1, ' ');
234        msg.setElement(2, '0');
235        msg.setElement(3, '5');
236        int j = 4;
237        for (int i = 0; i < contents.length; i++) {
238            msg.setElement(j++, ' ');
239            msg.addIntAsTwoHex(contents[i] & 0xFF, j);
240            j = j + 2;
241        }
242
243        // send it
244        _memo.getTrafficController().sendEasyDccMessage(msg, this);
245    }
246
247    /**
248     * Remove a Locomotive from an Advanced Consist
249     *
250     * @param LocoAddress is the Locomotive address to add to the locomotive
251     */
252    @Override
253    protected synchronized void removeFromAdvancedConsist(DccLocoAddress LocoAddress) {
254        if (log.isDebugEnabled()) {
255            log.debug(" Remove Locomotive {} from advanced consist {}", LocoAddress.toString(), consistAddress.toString());
256        }
257        // create the message and fill it,
258        byte[] contents = jmri.NmraPacket.consistControl(LocoAddress.getNumber(),
259                LocoAddress.isLongAddress(),
260                0, true);
261        EasyDccMessage msg = new EasyDccMessage(4 + 3 * contents.length);
262        msg.setOpCode('S');
263        msg.setElement(1, ' ');
264        msg.setElement(2, '0');
265        msg.setElement(3, '5');
266        int j = 4;
267        for (int i = 0; i < contents.length; i++) {
268            msg.setElement(j++, ' ');
269            msg.addIntAsTwoHex(contents[i] & 0xFF, j);
270            j = j + 2;
271        }
272
273        // send it
274        _memo.getTrafficController().sendEasyDccMessage(msg, this);
275    }
276
277    /**
278     * Add a Locomotive to an EasyDCC Standard Consist.
279     *
280     * @param LocoAddress is the Locomotive address to add to the locomotive
281     * @param directionNormal is True if the locomotive is traveling
282     *        the same direction as the consist, or false otherwise.
283     */
284    private synchronized void addToCSConsist(DccLocoAddress LocoAddress, boolean directionNormal) {
285        if (log.isDebugEnabled()) {
286            log.debug("Add Locomotive {} to Standard Consist {} With Direction Normal {}.", LocoAddress.toString(), consistAddress.toString(), directionNormal);
287        }
288        EasyDccMessage m;
289        if (directionNormal) {
290            m = EasyDccMessage.getAddConsistNormal(consistAddress.getNumber(), LocoAddress);
291        } else {
292            m = EasyDccMessage.getAddConsistReverse(consistAddress.getNumber(), LocoAddress);
293        }
294        _memo.getTrafficController().sendEasyDccMessage(m, this);
295    }
296
297    /**
298     * Remove a Locomotive from an EasyDCC Standard Consist.
299     *
300     * @param LocoAddress is the Locomotive address to add to the locomotive
301     */
302    public synchronized void removeFromCSConsist(DccLocoAddress LocoAddress) {
303        if (log.isDebugEnabled()) {
304            log.debug("Remove Locomotive {} from Standard Consist {}.", LocoAddress.toString(), consistAddress.toString());
305        }
306        EasyDccMessage m = EasyDccMessage.getSubtractConsist(consistAddress.getNumber(), LocoAddress);
307        _memo.getTrafficController().sendEasyDccMessage(m, this);
308    }
309
310    /**
311     * Listeners for messages from the command station.
312     */
313    @Override
314    public void message(EasyDccMessage m) {
315        log.error("message received unexpectedly: {}", m.toString());
316    }
317
318    @Override
319    public void reply(EasyDccReply r) {
320        // There isn't anything meaningful coming back at this time.
321        if (log.isDebugEnabled()) {
322            log.debug("reply received unexpectedly: {}", r.toString());
323        }
324    }
325
326    private final static Logger log = LoggerFactory.getLogger(EasyDccConsist.class);
327
328}