001package jmri.jmrix.loconet;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Hashtable;
006import java.util.List;
007import java.util.Vector;
008import javax.annotation.Nonnull;
009import jmri.CommandStation;
010import jmri.ProgListener;
011import jmri.Programmer;
012import jmri.ProgrammingMode;
013import jmri.jmrix.AbstractProgrammer;
014import jmri.jmrix.loconet.SlotMapEntry.SlotType;
015
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019/**
020 * Controls a collection of slots, acting as the counter-part of a LocoNet
021 * command station.
022 * <p>
023 * A SlotListener can register to hear changes. By registering here, the
024 * SlotListener is saying that it wants to be notified of a change in any slot.
025 * Alternately, the SlotListener can register with some specific slot, done via
026 * the LocoNetSlot object itself.
027 * <p>
028 * Strictly speaking, functions 9 through 28 are not in the actual slot, but
029 * it's convenient to imagine there's an "extended slot" and keep track of them
030 * here. This is a partial implementation, though, because setting is still done
031 * directly in {@link LocoNetThrottle}. In particular, if this slot has not been
032 * read from the command station, the first message directly setting F9 through
033 * F28 will not have a place to store information. Instead, it will trigger a
034 * slot read, so the following messages will be properly handled.
035 * <p>
036 * Some of the message formats used in this class are Copyright Digitrax, Inc.
037 * and used with permission as part of the JMRI project. That permission does
038 * not extend to uses in other software products. If you wish to use this code,
039 * algorithm or these message formats outside of JMRI, please contact Digitrax
040 * Inc for separate permission.
041 * <p>
042 * This Programmer implementation is single-user only. It's not clear whether
043 * the command stations can have multiple programming requests outstanding (e.g.
044 * service mode and ops mode, or two ops mode) at the same time, but this code
045 * definitely can't.
046 *
047 * @author Bob Jacobsen Copyright (C) 2001, 2003
048 * @author B. Milhaupt, Copyright (C) 2018
049 */
050public class SlotManager extends AbstractProgrammer implements LocoNetListener, CommandStation {
051
052    /**
053     * Time to wait after programming operation complete on LocoNet
054     * before reporting completion and hence starting next operation
055     */
056    static public int postProgDelay = 100; // this is public to allow changes via script
057
058    public int slotScanInterval = 50; // this is public to allow changes via script and tests
059
060    public int serviceModeReplyDelay = 20;  // this is public to allow changes via script and tests
061
062    public int opsModeReplyDelay = 100;  // this is public to allow changes via script and tests. Adjusted by UsbDcs210PlusAdapter
063
064    public boolean pmManagerGotReply = false;  //this is public to allow changes via script and tests
065
066    public boolean supportsSlot250;
067
068     /**
069     * a Map of the CS slots.
070     */
071    public List<SlotMapEntry> slotMap = new ArrayList<SlotMapEntry>();
072
073    /**
074     * Constructor for a SlotManager on a given TrafficController.
075     *
076     * @param tc Traffic Controller to be used by SlotManager for communication
077     *          with LocoNet
078     */
079    public SlotManager(LnTrafficController tc) {
080        this.tc = tc;
081
082        // change timeout values from AbstractProgrammer superclass
083        LONG_TIMEOUT = 180000;  // Fleischmann command stations take forever
084        SHORT_TIMEOUT = 8000;   // DCS240 reads
085
086        // dummy slot map until command station set (if ever)
087        slotMap = Arrays.asList(new SlotMapEntry(0,0,SlotType.SYSTEM),
088                    new SlotMapEntry(1,120,SlotType.LOCO),
089                    new SlotMapEntry(121,127,SlotType.SYSTEM),
090                    new SlotMapEntry(128,247,SlotType.UNKNOWN),
091                    new SlotMapEntry(248,256,SlotType.SYSTEM),   // potential stat slots
092                    new SlotMapEntry(257,375,SlotType.UNKNOWN),
093                    new SlotMapEntry(376,384,SlotType.SYSTEM),
094                    new SlotMapEntry(385,432,SlotType.UNKNOWN));
095
096        loadSlots(true);
097
098        // listen to the LocoNet
099        tc.addLocoNetListener(~0, this);
100
101    }
102
103    /**
104     * Initialize the slots array.
105     * @param initialize if true a new slot is created else it is just updated with type
106     *                  and protocol
107     */
108    protected void loadSlots(boolean initialize) {
109        // initialize slot array
110        for (SlotMapEntry item : slotMap) {
111            for (int slotIx = item.getFrom(); slotIx <= item.getTo() ; slotIx++) {
112                if (initialize) {
113                    _slots[slotIx] = new LocoNetSlot( slotIx,getLoconetProtocol(),item.getSlotType());
114                }
115                else {
116                    _slots[slotIx].setSlotType(item.getSlotType());
117                }
118            }
119        }
120    }
121
122    protected LnTrafficController tc;
123
124    /**
125     * Send a DCC packet to the rails. This implements the CommandStation
126     * interface.  This mechanism can pass any valid NMRA packet of up to
127     * 6 data bytes (including the error-check byte).
128     *
129     * When available, these messages are forwarded to LocoNet using a
130     * "throttledTransmitter".  This decreases the speed with which these
131     * messages are sent, resulting in lower throughput, but fewer
132     * rejections by the command station on account of "buffer-overflow".
133     *
134     * @param packet  the data bytes of the raw NMRA packet to be sent.  The
135     *          "error check" byte must be included, even though the LocoNet
136     *          message will not include that byte; the command station
137     *          will re-create the error byte from the bytes encoded in
138     *          the LocoNet message.  LocoNet is unable to propagate packets
139     *          longer than 6 bytes (including the error-check byte).
140     *
141     * @param sendCount  the total number of times the packet is to be
142     *          sent on the DCC track signal (not LocoNet!).  Valid range is
143     *          between 1 and 8.  sendCount will be forced to this range if it
144     *          is outside of this range.
145     */
146    @Override
147    public boolean sendPacket(byte[] packet, int sendCount) {
148        if (sendCount > 8) {
149            log.warn("Ops Mode Accessory Packet 'Send count' reduced from {} to 8.", sendCount); // NOI18N
150            sendCount = 8;
151        }
152        if (sendCount < 1) {
153            log.warn("Ops Mode Accessory Packet 'Send count' of {} is illegal and is forced to 1.", sendCount); // NOI18N
154            sendCount = 1;
155        }
156        if (packet.length <= 1) {
157            log.error("Invalid DCC packet length: {}", packet.length); // NOI18N
158        }
159        if (packet.length > 6) {
160            log.error("DCC packet length is too great: {} bytes were passed; ignoring the request. ", packet.length); // NOI18N
161        }
162
163        LocoNetMessage m = new LocoNetMessage(11);
164        m.setElement(0, LnConstants.OPC_IMM_PACKET);
165        m.setElement(1, 0x0B);
166        m.setElement(2, 0x7F);
167        // the incoming packet includes a check byte that's not included in LocoNet packet
168        int length = packet.length - 1;
169
170        m.setElement(3, ((sendCount - 1) & 0x7) + 16 * (length & 0x7));
171
172        int highBits = 0;
173        if (length >= 1 && ((packet[0] & 0x80) != 0)) {
174            highBits |= 0x01;
175        }
176        if (length >= 2 && ((packet[1] & 0x80) != 0)) {
177            highBits |= 0x02;
178        }
179        if (length >= 3 && ((packet[2] & 0x80) != 0)) {
180            highBits |= 0x04;
181        }
182        if (length >= 4 && ((packet[3] & 0x80) != 0)) {
183            highBits |= 0x08;
184        }
185        if (length >= 5 && ((packet[4] & 0x80) != 0)) {
186            highBits |= 0x10;
187        }
188        m.setElement(4, highBits);
189
190        m.setElement(5, 0);
191        m.setElement(6, 0);
192        m.setElement(7, 0);
193        m.setElement(8, 0);
194        m.setElement(9, 0);
195        for (int i = 0; i < packet.length - 1; i++) {
196            m.setElement(5 + i, packet[i] & 0x7F);
197        }
198
199        if (throttledTransmitter != null) {
200            throttledTransmitter.sendLocoNetMessage(m);
201        } else {
202            tc.sendLocoNetMessage(m);
203        }
204        return true;
205    }
206
207    /*
208     * command station switches
209     */
210    private final int SLOTS_DCS240 = 433;
211    private int numSlots = SLOTS_DCS240;         // This is the largest number so far.
212    private int slot248CommandStationType;
213    private int slot248CommandStationSerial;
214    private int slot250InUseSlots;
215    private int slot250IdleSlots;
216    private int slot250FreeSlots;
217
218    /**
219     * The network protocol.
220     */
221    private int loconetProtocol = LnConstants.LOCONETPROTOCOL_UNKNOWN;    // defaults to unknown
222
223    /**
224     *
225     * @param value the loconet protocol supported
226     */
227    public void setLoconet2Supported(int value) {
228        loconetProtocol = value;
229    }
230
231    /**
232     * Get the Command Station type reported in slot 248 message
233     * @return model
234     */
235    public String getSlot248CommandStationType() {
236        return LnConstants.IPL_NAME(slot248CommandStationType);
237    }
238
239    /**
240     * Get the total number of slots reported in the slot250 message;
241     * @return number of slots
242     */
243    public int getSlot250CSSlots() {
244        return slot250InUseSlots + slot250IdleSlots + slot250FreeSlots;
245    }
246
247    /**
248     *
249     * @return the loconet protocol supported
250     */
251    public int getLoconetProtocol() {
252        return loconetProtocol;
253    }
254
255    /**
256     * Information on slot state is stored in an array of LocoNetSlot objects.
257     * This is declared final because we never need to modify the array itself,
258     * just its contents.
259     */
260    protected LocoNetSlot _slots[] = new LocoNetSlot[getNumSlots()];
261
262    /**
263     * Access the information in a specific slot. Note that this is a mutable
264     * access, so that the information in the LocoNetSlot object can be changed.
265     *
266     * @param i Specific slot, counted starting from zero.
267     * @return The Slot object
268     */
269    public LocoNetSlot slot(int i) {
270        return _slots[i];
271    }
272
273    public int getNumSlots() {
274        return numSlots;
275    }
276    /**
277     * Obtain a slot for a particular loco address.
278     * <p>
279     * This requires access to the command station, even if the locomotive
280     * address appears in the current contents of the slot array, to ensure that
281     * our local image is up-to-date.
282     * <p>
283     * This method sends an info request. When the echo of this is returned from
284     * the LocoNet, the next slot-read is recognized as the response.
285     * <p>
286     * The object that's looking for this information must provide a
287     * SlotListener to notify when the slot ID becomes available.
288     * <p>
289     * The SlotListener is not subscribed for slot notifications; it can do that
290     * later if it wants. We don't currently think that's a race condition.
291     *
292     * @param i Specific slot, counted starting from zero.
293     * @param l The SlotListener to notify of the answer.
294     */
295    public void slotFromLocoAddress (int i, SlotListener l) {
296        // store connection between this address and listener for later
297        mLocoAddrHash.put(Integer.valueOf(i), l);
298
299        // send info request
300        LocoNetMessage m = new LocoNetMessage(4);
301        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) {
302            m.setOpCode(LnConstants.OPC_LOCO_ADR);  // OPC_LOCO_ADR
303        } else {
304            m.setOpCode(LnConstants.OPC_EXP_REQ_SLOT); //  Extended slot
305        }
306        m.setElement(1, (i / 128) & 0x7F);
307        m.setElement(2, i & 0x7F);
308        tc.sendLocoNetMessage(m);
309    }
310
311    javax.swing.Timer staleSlotCheckTimer = null;
312
313    /**
314     * Scan the slot array looking for slots that are in-use or common but have
315     * not had any updates in over 90s and issue a read slot request to update
316     * their state as the command station may have purged or stopped updating
317     * the slot without telling us via a LocoNet message.
318     * <p>
319     * This is intended to be called from the staleSlotCheckTimer
320     */
321    private void checkStaleSlots() {
322        long staleTimeout = System.currentTimeMillis() - 90000; // 90 seconds ago
323        LocoNetSlot slot;
324
325        // We will just check the normal loco slots 1 to numSlots exclude systemslots
326        for (int i = 1; i < numSlots; i++) {
327            slot = _slots[i];
328            if (!slot.isSystemSlot()) {
329                if ((slot.slotStatus() == LnConstants.LOCO_IN_USE || slot.slotStatus() == LnConstants.LOCO_COMMON)
330                    && (slot.getLastUpdateTime() <= staleTimeout)) {
331                    sendReadSlot(i);
332                    break; // only send the first one found
333                }
334            }
335        }
336    }
337
338
339    java.util.TimerTask slot250Task = null;
340    /**
341     * Request slot data for 248 and 250
342     * Runs delayed
343     * <p>
344     * A call is trigger after the first slot response (PowerManager) received.
345     */
346    private void pollSpecialSlots() {
347        sendReadSlot(248);
348        slot250Task = new java.util.TimerTask() {
349            @Override
350            public void run() {
351                try {
352                    sendReadSlot(250);
353                } catch (Exception e) {
354                    log.error("Exception occurred while checking slot250", e);
355                }
356            }
357        };
358        jmri.util.TimerUtil.schedule(slot250Task,100);
359    }
360
361    /**
362     * Provide a mapping between locomotive addresses and the SlotListener
363     * that's interested in them.
364     */
365    Hashtable<Integer, SlotListener> mLocoAddrHash = new Hashtable<>();
366
367    // data members to hold contact with the slot listeners
368    final private Vector<SlotListener> slotListeners = new Vector<>();
369
370    /**
371     * Add a slot listener, if it is not already registered
372     * <p>
373     * The slot listener will be invoked every time a slot changes state.
374     *
375     * @param l Slot Listener to be added
376     */
377    public synchronized void addSlotListener(SlotListener l) {
378        // add only if not already registered
379        if (!slotListeners.contains(l)) {
380            slotListeners.addElement(l);
381        }
382    }
383
384    /**
385     * Add a slot listener, if it is registered.
386     * <p>
387     * The slot listener will be removed from the list of listeners which are
388     * invoked whenever a slot changes state.
389     *
390     * @param l Slot Listener to be removed
391     */
392    public synchronized void removeSlotListener(SlotListener l) {
393        if (slotListeners.contains(l)) {
394            slotListeners.removeElement(l);
395        }
396    }
397
398    /**
399     * Trigger the notification of all SlotListeners.
400     *
401     * @param s The changed slot to notify.
402     */
403    @SuppressWarnings("unchecked")
404    protected void notify(LocoNetSlot s) {
405        // make a copy of the listener vector to synchronized not needed for transmit
406        Vector<SlotListener> v;
407        synchronized (this) {
408            v = (Vector<SlotListener>) slotListeners.clone();
409        }
410        log.debug("notify {} SlotListeners about slot {}", // NOI18N
411                v.size(), s.getSlot());
412        // forward to all listeners
413        int cnt = v.size();
414        for (int i = 0; i < cnt; i++) {
415            SlotListener client = v.elementAt(i);
416            client.notifyChangedSlot(s);
417        }
418    }
419
420    LocoNetMessage immedPacket;
421
422    /**
423     * Listen to the LocoNet. This is just a steering routine, which invokes
424     * others for the various processing steps.
425     *
426     * @param m incoming message
427     */
428    @Override
429    public void message(LocoNetMessage m) {
430        if (m.getOpCode() == LnConstants.OPC_RE_LOCORESET_BUTTON) {
431            if (commandStationType.getSupportsLocoReset()) {
432                // Command station LocoReset button was triggered.
433                //
434                // Note that sending a LocoNet message using this OpCode to the command
435                // station does _not_ seem to trigger the equivalent effect; only
436                // pressing the button seems to do so.
437                // If the OpCode is received by JMRI, regardless of its source,
438                // JMRI will simply trigger a re-read of all slots.  This will
439                // allow the JMRI slots to stay consistent with command station
440                // slot information, regardless of whether the command station
441                // just modified the slot information.
442                javax.swing.Timer t = new javax.swing.Timer(500, (java.awt.event.ActionEvent e) -> {
443                    log.debug("Updating slots account received opcode 0x8a message");   // NOI18N
444                    update(slotMap,slotScanInterval);
445                });
446                t.stop();
447                t.setInitialDelay(500);
448                t.setRepeats(false);
449                t.start();
450            }
451            return;
452        }
453
454        // LACK processing for resend of immediate command
455        if (!mTurnoutNoRetry && immedPacket != null &&
456                m.getOpCode() == LnConstants.OPC_LONG_ACK &&
457                m.getElement(1) == 0x6D && m.getElement(2) == 0x00) {
458            // LACK reject, resend immediately
459            tc.sendLocoNetMessage(immedPacket);
460            immedPacket = null;
461        }
462        if (m.getOpCode() == LnConstants.OPC_IMM_PACKET &&
463                m.getElement(1) == 0x0B && m.getElement(2) == 0x7F) {
464            immedPacket = m;
465        } else {
466            immedPacket = null;
467        }
468
469        // slot specific message?
470        int i = findSlotFromMessage(m);
471        if (i != -1) {
472            getMoreDetailsForSlot(m, i);
473            checkSpecialSlots(m, i);
474            forwardMessageToSlot(m, i);
475            respondToAddrRequest(m, i);
476            programmerOpMessage(m, i);
477            checkLoconetProtocol(m,i);
478        }
479
480        // LONG_ACK response?
481        if (m.getOpCode() == LnConstants.OPC_LONG_ACK) {
482            handleLongAck(m);
483        }
484
485        // see if extended function message
486        if (isExtFunctionMessage(m)) {
487            // yes, get address
488            int addr = getDirectFunctionAddress(m);
489            // find slot(s) containing this address
490            // and route message to them
491            boolean found = false;
492            for (int j = 0; j < 120; j++) {
493                LocoNetSlot slot = slot(j);
494                if (slot == null) {
495                    continue;
496                }
497                if ((slot.locoAddr() != addr)
498                        || (slot.slotStatus() == LnConstants.LOCO_FREE)) {
499                    continue;
500                }
501                // found!
502                slot.functionMessage(getDirectDccPacket(m));
503                found = true;
504            }
505            if (!found) {
506                // rats! Slot not loaded since program start.  Request it be
507                // reloaded for later, but that'll be too late
508                // for this one.
509                LocoNetMessage mo = new LocoNetMessage(4);
510                mo.setOpCode(LnConstants.OPC_LOCO_ADR);  // OPC_LOCO_ADR
511                mo.setElement(1, (addr / 128) & 0x7F);
512                mo.setElement(2, addr & 0x7F);
513                tc.sendLocoNetMessage(mo);
514            }
515        }
516    }
517
518    /*
519     * Collect data from specific slots
520     */
521    void checkSpecialSlots(LocoNetMessage m, int slot) {
522        if (!pmManagerGotReply && slot == 0 &&
523                (m.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA || m.getOpCode() == LnConstants.OPC_SL_RD_DATA)) {
524            pmManagerGotReply = true;
525            if (supportsSlot250) {
526                pollSpecialSlots();
527            }
528            return;
529        }
530        switch (slot) {
531            case 250:
532                // slot info if we have serial, the serial number in this slot
533                // does not indicate whether in booster or cs mode.
534                if (slot248CommandStationSerial == ((m.getElement(19) & 0x3F) * 128) + m.getElement(18)) {
535                    slot250InUseSlots = (m.getElement(4) + ((m.getElement(5) & 0x03) * 128));
536                    slot250IdleSlots = (m.getElement(6) + ((m.getElement(7) & 0x03) * 128));
537                    slot250FreeSlots = (m.getElement(8) + ((m.getElement(9) & 0x03) * 128));
538                }
539                break;
540            case 248:
541                // Base HW Information
542                // If a CS in CS mode then byte 19 bit 6 in on. else its in
543                // booster mode
544                // The device type is in byte 14
545                if ((m.getElement(19) & 0x40) == 0x40) {
546                    slot248CommandStationSerial = ((m.getElement(19) & 0x3F) * 128) + m.getElement(18);
547                    slot248CommandStationType = m.getElement(14);
548                }
549                break;
550            default:
551        }
552    }
553
554    /*
555     * If protocol not yet established use slot status for protocol support
556     * System slots , except zero, do not have this info
557     */
558    void checkLoconetProtocol(LocoNetMessage m, int slot) {
559        // detect protocol if not yet set
560        if (getLoconetProtocol() == LnConstants.LOCONETPROTOCOL_UNKNOWN) {
561            if (_slots[slot].getSlotType() != SlotType.SYSTEM || slot == 0) {
562                if ((m.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA && m.getNumDataElements() == 21) ||
563                        (m.getOpCode() == LnConstants.OPC_SL_RD_DATA)) {
564                    if ((m.getElement(7) & 0b01000000) == 0b01000000) {
565                        log.info("Setting protocol Loconet 2");
566                        setLoconet2Supported(LnConstants.LOCONETPROTOCOL_TWO);
567                    } else {
568                        log.info("Setting protocol Loconet 1");
569                        setLoconet2Supported(LnConstants.LOCONETPROTOCOL_ONE);
570                    }
571                }
572            }
573        }
574    }
575
576    /**
577     * Checks a LocoNet message to see if it encodes a DCC "direct function" packet.
578     *
579     * @param m  a LocoNet Message
580     * @return the loco address if the LocoNet message encodes a "direct function" packet,
581     * else returns -1
582     */
583    int getDirectFunctionAddress(LocoNetMessage m) {
584        if (m.getOpCode() != LnConstants.OPC_IMM_PACKET) {
585            return -1;
586        }
587        if (m.getElement(1) != 0x0B) {
588            return -1;
589        }
590        if (m.getElement(2) != 0x7F) {
591            return -1;
592        }
593        // Direct packet, check length
594        if ((m.getElement(3) & 0x70) < 0x20) {
595            return -1;
596        }
597        int addr = -1;
598        // check long address
599        if ((m.getElement(4) & 0x01) == 0) { //bit 7=0 short
600            addr = (m.getElement(5) & 0xFF);
601            if ((m.getElement(4) & 0x01) != 0) {
602                addr += 128;  // and high bit
603            }
604        } else if ((m.getElement(5) & 0x40) == 0x40) { // bit 7 = 1 if bit 6 = 1 then long
605            addr = (m.getElement(5) & 0x3F) * 256 + (m.getElement(6) & 0xFF);
606            if ((m.getElement(4) & 0x02) != 0) {
607                addr += 128;  // and high bit
608            }
609        } else { // accessory decoder or extended accessory decoder
610            addr = (m.getElement(5) & 0x3F);
611        }
612        return addr;
613    }
614
615    /**
616     * Extracts a DCC "direct packet" from a LocoNet message, if possible.
617     * <p>
618     * if this is a direct DCC packet, return as one long
619     * else return -1. Packet does not include address bytes.
620     *
621     * @param m a LocoNet message to be inspected
622     * @return an integer containing the bytes of the DCC packet, except the address bytes.
623     */
624    int getDirectDccPacket(LocoNetMessage m) {
625        if (m.getOpCode() != LnConstants.OPC_IMM_PACKET) {
626            return -1;
627        }
628        if (m.getElement(1) != 0x0B) {
629            return -1;
630        }
631        if (m.getElement(2) != 0x7F) {
632            return -1;
633        }
634        // Direct packet, check length
635        if ((m.getElement(3) & 0x70) < 0x20) {
636            return -1;
637        }
638        int result = 0;
639        int n = (m.getElement(3) & 0xF0) / 16;
640        int start;
641        int high = m.getElement(4);
642        // check long or short address
643        if ((m.getElement(4) & 0x01) == 1 && (m.getElement(5) & 0x40) == 0x40 ) {  //long address bit 7 im1 = 1 and bit6 im1 = 1
644            start = 7;
645            high = high >> 2;
646            n = n - 2;
647         } else {  //short or accessory
648            start = 6;
649            high = high >> 1;
650            n = n - 1;
651        }
652        // get result
653        for (int i = 0; i < n; i++) {
654            result = result * 256 + (m.getElement(start + i) & 0x7F);
655            if ((high & 0x01) != 0) {
656                result += 128;
657            }
658            high = high >> 1;
659        }
660        return result;
661    }
662
663    /**
664     * Determines if a LocoNet message encodes a direct request to control
665     * DCC functions F9 thru F28
666     *
667     * @param m the LocoNet message to be evaluated
668     * @return true if the message is an external DCC packet request for F9-F28,
669     *      else false.
670     */
671    boolean isExtFunctionMessage(LocoNetMessage m) {
672        int pkt = getDirectDccPacket(m);
673        if (pkt < 0) {
674            return false;
675        }
676        // check F9-12
677        if ((pkt & 0xFFFFFF0) == 0xA0) {
678            return true;
679        }
680        // check F13-28
681        if ((pkt & 0xFFFFFE00) == 0xDE00) {
682            return true;
683        }
684        return false;
685    }
686
687    /**
688     * Extracts the LocoNet slot number from a LocoNet message, if possible.
689     * <p>
690     * Find the slot number that a message references
691     * <p>
692     * This routine only looks for explicit slot references; it does not, for example,
693     * identify a loco address in the message and then work thru the slots to find a
694     * slot which references that loco address.
695     *
696     * @param m LocoNet Message to be inspected
697     * @return an integer representing the slot number encoded in the LocoNet
698     *          message, or -1 if the message does not contain a slot reference
699     */
700    public int findSlotFromMessage(LocoNetMessage m) {
701
702        int i = -1;  // find the slot index in the message and store here
703
704        // decode the specific message type and hence slot number
705        switch (m.getOpCode()) {
706            case LnConstants.OPC_WR_SL_DATA:
707            case LnConstants.OPC_SL_RD_DATA:
708                i = m.getElement(2);
709                break;
710            case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL:
711                if ( m.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN) {
712                    i = m.getElement(2);
713                    break;
714                }
715                i = ( (m.getElement(1) & 0x03 ) *128) + m.getElement(2);
716                break;
717            case LnConstants.OPC_LOCO_DIRF:
718            case LnConstants.OPC_LOCO_SND:
719            case LnConstants.OPC_LOCO_SPD:
720            case LnConstants.OPC_SLOT_STAT1:
721            case LnConstants.OPC_LINK_SLOTS:
722            case LnConstants.OPC_UNLINK_SLOTS:
723                i = m.getElement(1);
724                break;
725
726            case LnConstants.OPC_MOVE_SLOTS:  // No follow on for some moves
727                if (m.getElement(1) != 0) {
728                    i = m.getElement(1);
729                    return i;
730                }
731                break;
732            case LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR:
733                i = ( (m.getElement(1) & 0x03 ) *128) + m.getElement(2);
734                break;
735            case LnConstants.OPC_EXP_RD_SL_DATA:
736            case LnConstants.OPC_EXP_WR_SL_DATA:
737                //only certain lengths get passed to slot
738                if (m.getElement(1) == 21) {
739                    i = ( (m.getElement(2) & 0x03 ) *128) + m.getElement(3);
740                }
741                return i;
742            default:
743                // nothing here for us
744                return i;
745        }
746        // break gets to here
747        return i;
748    }
749
750    /**
751     * Check CV programming LONG_ACK message byte 1
752     * <p>
753     * The following methods are for parsing LACK as response to CV programming.
754     * It is divided into numerous small methods so that each bit can be
755     * overridden for special parsing for individual command station types.
756     *
757     * @param byte1 from the LocoNet message
758     * @return true if byte1 encodes a response to a OPC_SL_WRITE or an
759     *          Expanded Slot Write
760     */
761    protected boolean checkLackByte1(int byte1) {
762        if ((byte1 & 0xEF) == 0x6F) {
763            return true;
764        } else {
765            return false;
766        }
767    }
768
769    /**
770     * Checks the status byte of an OPC_LONG_ACK when performing CV programming
771     * operations.
772     *
773     * @param byte2 status byte
774     * @return True if status byte indicates acceptance of the command, else false.
775     */
776    protected boolean checkLackTaskAccepted(int byte2) {
777        if (byte2 == 1 // task accepted
778                || byte2 == 0x23 || byte2 == 0x2B || byte2 == 0x6B // added as DCS51 fix
779                // deliberately ignoring 0x7F varient, see okToIgnoreLack
780            ) {
781            return true;
782        } else {
783            return false;
784        }
785    }
786
787    /**
788     * Checks the OPC_LONG_ACK status byte response to a programming
789     * operation.
790     *
791     * @param byte2 from the OPC_LONG_ACK message
792     * @return true if the programmer returned "busy" else false
793     */
794    protected boolean checkLackProgrammerBusy(int byte2) {
795        if (byte2 == 0) {
796            return true;
797        } else {
798            return false;
799        }
800    }
801
802    /**
803     * Checks the OPC_LONG_ACK status byte response to a programming
804     * operation to see if the programmer accepted the operation "blindly".
805     *
806     * @param byte2 from the OPC_LONG_ACK message
807     * @return true if the programmer indicated a "blind operation", else false
808     */
809    protected boolean checkLackAcceptedBlind(int byte2) {
810        if (byte2 == 0x40) {
811            return true;
812        } else {
813            return false;
814        }
815    }
816
817    /**
818     * Some LACKs with specific OPC_LONG_ACK status byte values can just be ignored.
819     *
820     * @param byte2 from the OPC_LONG_ACK message
821     * @return true if this form of LACK can be ignored without a warning message
822     */
823    protected boolean okToIgnoreLack(int byte2) {
824        if (byte2 == 0x7F ) {
825            return true;
826        } else {
827            return false;
828        }
829    }
830
831    private boolean acceptAnyLACK = false;
832    /**
833     * Indicate that the command station LONG_ACK response details can be ignored
834     * for this operation.  Typically this is used when accessing Loconet-attached boards.
835     */
836    public final void setAcceptAnyLACK() {
837        acceptAnyLACK = true;
838    }
839
840    /**
841     * Handles OPC_LONG_ACK replies to programming slot operations.
842     *
843     * @param m LocoNet message being analyzed
844     */
845    protected void handleLongAck(LocoNetMessage m) {
846        // handle if reply to slot. There's no slot number in the LACK, unfortunately.
847        // If this is a LACK to a Slot op, and progState is command pending,
848        // assume its for us...
849        log.debug("LACK in state {} message: {}", progState, m.toString()); // NOI18N
850        if (checkLackByte1(m.getElement(1)) && progState == 1) {
851            // in programming state
852            if (acceptAnyLACK) {
853                log.debug("accepted LACK {} via acceptAnyLACK", m.getElement(2));
854                // Any form of LACK response from CS is accepted here.
855                // Loconet-attached decoders (LOCONETOPSBOARD) receive the program commands
856                // directly via loconet and respond as required without needing any CS action,
857                // making the details of the LACK response irrelevant.
858                if (_progRead || _progConfirm) {
859                    // move to commandExecuting state
860                    startShortTimer();
861                    progState = 2;
862                } else {
863                    // move to not programming state
864                    progState = 0;
865                    stopTimer();
866                    // allow the target device time to execute then notify ProgListener
867                    notifyProgListenerEndAfterDelay();
868                }
869                acceptAnyLACK = false;      // restore normal state for next operation
870            }
871            // check status byte
872            else if (checkLackTaskAccepted(m.getElement(2))) { // task accepted
873                // 'not implemented' (op on main)
874                // but BDL16 and other devices can eventually reply, so
875                // move to commandExecuting state
876                log.debug("checkLackTaskAccepted accepted, next state 2"); // NOI18N
877                if ((_progRead || _progConfirm) && mServiceMode) {
878                    startLongTimer();
879                } else {
880                    startShortTimer();
881                }
882                progState = 2;
883            } else if (checkLackProgrammerBusy(m.getElement(2))) { // task aborted as busy
884                // move to not programming state
885                progState = 0;
886                // notify user ProgListener
887                stopTimer();
888                notifyProgListenerLack(jmri.ProgListener.ProgrammerBusy);
889            } else if (checkLackAcceptedBlind(m.getElement(2))) { // task accepted blind
890                if ((_progRead || _progConfirm) && !mServiceMode) { // incorrect Reserved OpSw setting can cause this response to OpsMode Read
891                    // just treat it as a normal OpsMode Read response
892                    // move to commandExecuting state
893                    log.debug("LACK accepted (ignoring incorrect OpSw), next state 2"); // NOI18N
894                    startShortTimer();
895                    progState = 2;
896                } else {
897                    // move to not programming state
898                    progState = 0;
899                    stopTimer();
900                    // allow command station time to execute then notify ProgListener
901                    notifyProgListenerEndAfterDelay();
902                }
903            } else if (okToIgnoreLack(m.getElement(2))) {
904                // this form of LACK can be silently ignored
905                log.debug("Ignoring LACK with {}", m.getElement(2));
906            } else { // not sure how to cope, so complain
907                log.warn("unexpected LACK reply code {}", m.getElement(2)); // NOI18N
908                // move to not programming state
909                progState = 0;
910                // notify user ProgListener
911                stopTimer();
912                notifyProgListenerLack(jmri.ProgListener.UnknownError);
913            }
914        }
915    }
916
917    /**
918     * Internal method to notify ProgListener after a short delay that the operation is complete.
919     * The delay ensures that the target device has completed the operation prior to the notification.
920     */
921    protected void notifyProgListenerEndAfterDelay() {
922        javax.swing.Timer timer = new javax.swing.Timer(postProgDelay, new java.awt.event.ActionListener() {
923            @Override
924            public void actionPerformed(java.awt.event.ActionEvent e) {
925                notifyProgListenerEnd(-1, 0); // no value (e.g. -1), no error status (e.g.0)
926            }
927        });
928        timer.stop();
929        timer.setInitialDelay(postProgDelay);
930        timer.setRepeats(false);
931        timer.start();
932    }
933
934    /**
935     * Forward Slot-related LocoNet message to the slot.
936     *
937     * @param m a LocoNet message targeted at a slot
938     * @param i the slot number to which the LocoNet message is targeted.
939     */
940    public void forwardMessageToSlot(LocoNetMessage m, int i) {
941
942        // if here, i holds the slot number, and we expect to be able to parse
943        // and have the slot handle the message
944        if (i >= _slots.length || i < 0) {
945            log.error("Received slot number {} is greater than array length {} Message was {}", // NOI18N
946                    i, _slots.length, m.toString()); // NOI18N
947            return; // prevents array index out-of-bounds when referencing _slots[i]
948        }
949
950        if ( !validateSlotNumber(i)) {
951            log.warn("Received slot number {} is not in the slot map, have you defined the wrong cammand station type? Message was {}",
952                   i,  m.toString());
953        }
954
955        try {
956            _slots[i].setSlot(m);
957        } catch (LocoNetException e) {
958            // must not have been interesting, or at least routed right
959            log.error("slot rejected LocoNetMessage {}", m); // NOI18N
960            return;
961        } catch (Exception e) {
962            log.error("Unexplained error _slots[{}].setSlot({})",i,m,e);
963            return;
964        }
965        // notify listeners that slot may have changed
966        notify(_slots[i]);
967    }
968
969    /**
970     * A sort of slot listener which handles loco address requests
971     *
972     * @param m a LocoNet message
973     * @param i the slot to which it is directed
974     */
975    protected void respondToAddrRequest(LocoNetMessage m, int i) {
976        // is called any time a LocoNet message is received.  Note that we do _NOT_ know why a given message happens!
977
978        // if this is OPC_SL_RD_DATA
979        if (m.getOpCode() == LnConstants.OPC_SL_RD_DATA || m.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA ) {
980            // yes, see if request exists
981            // note that the appropriate _slots[] entry has already been updated
982            // to reflect the content of the LocoNet message, so _slots[i]
983            // has the locomotive address of this request
984            int addr = _slots[i].locoAddr();
985            log.debug("LOCO_ADR resp is slot {} for addr {}", i, addr); // NOI18N
986            SlotListener l = mLocoAddrHash.get(Integer.valueOf(addr));
987            if (l != null) {
988                // only notify once per request
989                mLocoAddrHash.remove(Integer.valueOf(addr));
990                // and send the notification
991                log.debug("notify listener"); // NOI18N
992                l.notifyChangedSlot(_slots[i]);
993            } else {
994                log.debug("no request for addr {}", addr); // NOI18N
995            }
996        }
997    }
998
999    /**
1000     * If it is a slot being sent COMMON,
1001     *  after a delay, get the new status of the slot
1002     * If it is a true slot move, not dispatch or null
1003     *  after a delay, get the new status of the from slot, which varies by CS.
1004     *  the to slot should come in the reply.
1005     * @param m a LocoNet message
1006     * @param i the slot to which it is directed
1007     */
1008    protected void getMoreDetailsForSlot(LocoNetMessage m, int i) {
1009        // is called any time a LocoNet message is received.
1010        // sets up delayed slot read to update our effected slots to match the CS
1011        if (m.getOpCode() == LnConstants.OPC_SLOT_STAT1 &&
1012                ((m.getElement(2) & LnConstants.LOCOSTAT_MASK) == LnConstants.LOCO_COMMON ) ) {
1013            // Changing a slot to common. Depending on a CS and its OpSw, and throttle speed
1014            // it could have its status changed a number of ways.
1015            sendReadSlotDelayed(i,100);
1016        } else if (m.getOpCode() == LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL) {
1017            boolean isSettingStatus = ((m.getElement(3) & 0b01110000) == 0b01100000);
1018            if (isSettingStatus) {
1019                int stat = m.getElement(4);
1020                if ((stat & LnConstants.LOCOSTAT_MASK) == LnConstants.LOCO_COMMON) {
1021                    sendReadSlotDelayed(i,100);
1022                }
1023            }
1024            boolean isUnconsisting = ((m.getElement(3) & 0b01110000) == 0b01010000);
1025            if (isUnconsisting) {
1026                // read lead slot
1027                sendReadSlotDelayed(slot(i).getLeadSlot(),100);
1028            }
1029            boolean isConsisting = ((m.getElement(3) & 0b01110000) == 0b01000000);
1030            if (isConsisting) {
1031                // read 2nd slot
1032                int slotTwo = ((m.getElement(3) & 0b00000011) * 128 )+ m.getElement(4);
1033                sendReadSlotDelayed(slotTwo,100);
1034            }
1035        } else if (m.getOpCode() == LnConstants.OPC_MOVE_SLOTS) {
1036            // if a true move get the new from slot status
1037            // the to slot status is sent in the reply, but not if dispatch or null
1038            // as those return slot info.
1039            int slotTwo;
1040            slotTwo = m.getElement(2);
1041            if (i != 0 && slotTwo != 0 && i != slotTwo) {
1042                sendReadSlotDelayed(i,100);
1043            }
1044        } else if (m.getOpCode() == LnConstants.OPC_LINK_SLOTS ||
1045                m.getOpCode() == LnConstants.OPC_UNLINK_SLOTS ) {
1046            // unlink and link return first slot by not second (to or from)
1047            // the to slot status is sent in the reply
1048            int slotTwo;
1049            slotTwo = m.getElement(2);
1050            if (i != 0 && slotTwo != 0) {
1051                sendReadSlotDelayed(slotTwo,100);
1052            }
1053       }
1054    }
1055
1056    /**
1057     * Schedule a delayed slot read.
1058     * @param slotNo - the slot.
1059     * @param delay - delay in msecs.
1060     */
1061    protected void sendReadSlotDelayed(int slotNo, long delay) {
1062        java.util.TimerTask meterTask = new java.util.TimerTask() {
1063            int slotNumber = slotNo;
1064
1065            @Override
1066            public void run() {
1067                try {
1068                    sendReadSlot(slotNumber);
1069                } catch (Exception e) {
1070                    log.error("Exception occurred sendReadSlotDelayed:", e);
1071                }
1072            }
1073        };
1074        jmri.util.TimerUtil.schedule(meterTask, delay);
1075    }
1076
1077    /**
1078     * Handle LocoNet messages related to CV programming operations
1079     *
1080     * @param m a LocoNet message
1081     * @param i the slot toward which the message is destined
1082     */
1083    protected void programmerOpMessage(LocoNetMessage m, int i) {
1084
1085        // start checking for programming operations in slot 124
1086        if (i == 124) {
1087            // here its an operation on the programmer slot
1088            log.debug("Prog Message {} for slot 124 in state {}", // NOI18N
1089                    m.getOpCodeHex(), progState); // NOI18N
1090            switch (progState) {
1091                case 0:   // notProgramming
1092                    break;
1093                case 1:   // commandPending: waiting for an (optional) LACK
1094                case 2:   // commandExecuting
1095                    // waiting for slot read, is it present?
1096                    if (m.getOpCode() == LnConstants.OPC_SL_RD_DATA) {
1097                        log.debug("  was OPC_SL_RD_DATA"); // NOI18N
1098                        // yes, this is the end
1099                        // move to not programming state
1100                        stopTimer();
1101                        progState = 0;
1102
1103                        // parse out value returned
1104                        int value = -1;
1105                        int status = 0;
1106                        if (_progConfirm) {
1107                            // read command, get value; check if OK
1108                            value = _slots[i].cvval();
1109                            if (value != _confirmVal) {
1110                                status = status | jmri.ProgListener.ConfirmFailed;
1111                            }
1112                        }
1113                        if (_progRead) {
1114                            // read command, get value
1115                            value = _slots[i].cvval();
1116                        }
1117                        // parse out status
1118                        if ((_slots[i].pcmd() & LnConstants.PSTAT_NO_DECODER) != 0) {
1119                            status = (status | jmri.ProgListener.NoLocoDetected);
1120                        }
1121                        if ((_slots[i].pcmd() & LnConstants.PSTAT_WRITE_FAIL) != 0) {
1122                            status = (status | jmri.ProgListener.NoAck);
1123                        }
1124                        if ((_slots[i].pcmd() & LnConstants.PSTAT_READ_FAIL) != 0) {
1125                            status = (status | jmri.ProgListener.NoAck);
1126                        }
1127                        if ((_slots[i].pcmd() & LnConstants.PSTAT_USER_ABORTED) != 0) {
1128                            status = (status | jmri.ProgListener.UserAborted);
1129                        }
1130
1131                        // and send the notification
1132                        notifyProgListenerEnd(value, status);
1133                    }
1134                    break;
1135                default:  // error!
1136                    log.error("unexpected programming state {}", progState); // NOI18N
1137                    break;
1138            }
1139        }
1140    }
1141
1142    ProgrammingMode csOpSwProgrammingMode = new ProgrammingMode(
1143            "LOCONETCSOPSWMODE",
1144            Bundle.getMessage("LOCONETCSOPSWMODE"));
1145
1146    // members for handling the programmer interface
1147
1148    /**
1149     * Return a list of ProgrammingModes supported by this interface
1150     * Types implemented here.
1151     *
1152     * @return a List of ProgrammingMode objects containing the supported
1153     *          programming modes.
1154     */
1155
1156    @Override
1157    @Nonnull
1158    public List<ProgrammingMode> getSupportedModes() {
1159        List<ProgrammingMode> ret = new ArrayList<>();
1160        ret.add(ProgrammingMode.DIRECTBYTEMODE);
1161        ret.add(ProgrammingMode.PAGEMODE);
1162        ret.add(ProgrammingMode.REGISTERMODE);
1163        ret.add(ProgrammingMode.ADDRESSMODE);
1164        ret.add(csOpSwProgrammingMode);
1165
1166        return ret;
1167    }
1168
1169    /**
1170     * Remember whether the attached command station needs a sequence sent after
1171     * programming. The default operation is implemented in doEndOfProgramming
1172     * and turns power back on by sending a GPON message.
1173     */
1174    private boolean mProgEndSequence = false;
1175
1176    /**
1177     * Remember whether the attached command station can read from Decoders.
1178     */
1179    private boolean mCanRead = true;
1180
1181    /**
1182     * Determine whether this Programmer implementation is capable of reading
1183     * decoder contents. This is entirely determined by the attached command
1184     * station, not the code here, so it refers to the mCanRead member variable
1185     * which is recording the known state of that.
1186     *
1187     * @return True if reads are possible
1188     */
1189    @Override
1190    public boolean getCanRead() {
1191        return mCanRead;
1192    }
1193
1194    /**
1195     * Return the write confirm mode implemented by the command station.
1196     * <p>
1197     * Service mode always checks for DecoderReply. (The DCS240 also seems to do
1198     * ReadAfterWrite, but that's not fully understood yet)
1199     *
1200     * @param addr This implementation ignores this parameter
1201     * @return the supported WriteConfirmMode
1202     */
1203    @Nonnull
1204    @Override
1205    public Programmer.WriteConfirmMode getWriteConfirmMode(String addr) { return WriteConfirmMode.DecoderReply; }
1206
1207    /**
1208     * Set the command station type to one of the known types in the
1209     * {@link LnCommandStationType} enum.
1210     *
1211     * @param value contains the command station type
1212     */
1213    public void setCommandStationType(LnCommandStationType value) {
1214        commandStationType = value;
1215        mCanRead = value.getCanRead();
1216        mProgEndSequence = value.getProgPowersOff();
1217        slotMap = commandStationType.getSlotMap();
1218        supportsSlot250 = value.getSupportsSlot250();
1219
1220        loadSlots(false);
1221
1222        // We will scan the slot table every 0.3 s for in-use slots that are stale
1223        final int slotScanDelay = 300; // Must be short enough that 128 can be scanned in 90 seconds, see checkStaleSlots()
1224        staleSlotCheckTimer = new javax.swing.Timer(slotScanDelay, new java.awt.event.ActionListener() {
1225            @Override
1226            public void actionPerformed(java.awt.event.ActionEvent e) {
1227                checkStaleSlots();
1228            }
1229        });
1230
1231        staleSlotCheckTimer.setRepeats(true);
1232        staleSlotCheckTimer.setInitialDelay(30000);  // wait a bit at startup
1233        staleSlotCheckTimer.start();
1234
1235    }
1236
1237    LocoNetThrottledTransmitter throttledTransmitter = null;
1238    boolean mTurnoutNoRetry = false;
1239
1240    /**
1241     * Provide a ThrottledTransmitter for sending immediate packets.
1242     *
1243     * @param value contains a LocoNetThrottledTransmitter object
1244     * @param m contains a boolean value indicating mTurnoutNoRetry
1245     */
1246    public void setThrottledTransmitter(LocoNetThrottledTransmitter value, boolean m) {
1247        throttledTransmitter = value;
1248        mTurnoutNoRetry = m;
1249    }
1250
1251    /**
1252     * Get the command station type.
1253     *
1254     * @return an LnCommandStationType object
1255     */
1256    public LnCommandStationType getCommandStationType() {
1257        return commandStationType;
1258    }
1259
1260    protected LnCommandStationType commandStationType = null;
1261
1262    /**
1263     * Internal routine to handle a timeout.
1264     */
1265    @Override
1266    synchronized protected void timeout() {
1267        log.debug("timeout fires in state {}", progState); // NOI18N
1268
1269        if (progState != 0) {
1270            // we're programming, time to stop
1271            log.debug("timeout while programming"); // NOI18N
1272
1273            // perhaps no communications present? Fail back to end of programming
1274            progState = 0;
1275            // and send the notification; error code depends on state
1276            if (progState == 2 && !mServiceMode) { // ops mode command executing,
1277                // so did talk to command station at first
1278                notifyProgListenerEnd(_slots[124].cvval(), jmri.ProgListener.NoAck);
1279            } else {
1280                // all others
1281                notifyProgListenerEnd(_slots[124].cvval(), jmri.ProgListener.FailedTimeout);
1282                // might be leaving power off, but that's currently up to user to fix
1283            }
1284            acceptAnyLACK = false;      // ensure cleared if timed out without getting a LACK
1285        }
1286    }
1287
1288    int progState = 0;
1289    // 1 is commandPending
1290    // 2 is commandExecuting
1291    // 0 is notProgramming
1292    boolean _progRead = false;
1293    boolean _progConfirm = false;
1294    int _confirmVal;
1295    boolean mServiceMode = true;
1296
1297    /**
1298     * Write a CV via Ops Mode programming.
1299     *
1300     * @param CVname CV number
1301     * @param val value to write to the CV
1302     * @param p programmer
1303     * @param addr address of decoder
1304     * @param longAddr true if the address is a long address
1305     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1306     */
1307    public void writeCVOpsMode(String CVname, int val, jmri.ProgListener p,
1308            int addr, boolean longAddr) throws jmri.ProgrammerException {
1309        final int CV = Integer.parseInt(CVname);
1310        lopsa = addr & 0x7f;
1311        hopsa = (addr / 128) & 0x7f;
1312        mServiceMode = false;
1313        doWrite(CV, val, p, 0x67);  // ops mode byte write, with feedback
1314    }
1315
1316    /**
1317     * Write a CV via the Service Mode programmer.
1318     *
1319     * @param cvNum CV id as String
1320     * @param val value to write to the CV
1321     * @param p programmer
1322     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1323     */
1324    @Override
1325    public void writeCV(String cvNum, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
1326        log.debug("writeCV(string): cvNum={}, value={}", cvNum, val);
1327        if (getMode().equals(csOpSwProgrammingMode)) {
1328            log.debug("cvOpSw mode write!");
1329            // handle Command Station OpSw programming here
1330            String[] parts = cvNum.split("\\.");
1331            if ((parts[0].equals("csOpSw")) && (parts.length==2)) {
1332                if (csOpSwAccessor == null) {
1333                    csOpSwAccessor = new CsOpSwAccess(adaptermemo, p);
1334                } else {
1335                    csOpSwAccessor.setProgrammerListener(p);
1336                }
1337                // perform the CsOpSwMode read access
1338                log.debug("going to try the opsw access");
1339                csOpSwAccessor.writeCsOpSw(cvNum, val, p);
1340                return;
1341
1342            } else {
1343                log.warn("rejecting the cs opsw access account unsupported CV name format");
1344                // unsupported format in "cv" name. Signal an error
1345                notifyProgListenerEnd(p, 1, ProgListener.SequenceError);
1346                return;
1347
1348            }
1349        } else {
1350            // regular CV case
1351            int CV = Integer.parseInt(cvNum);
1352
1353            lopsa = 0;
1354            hopsa = 0;
1355            mServiceMode = true;
1356            // parse the programming command
1357            int pcmd = 0x43;       // LPE implies 0x40, but 0x43 is observed
1358            if (getMode().equals(ProgrammingMode.PAGEMODE)) {
1359                pcmd = pcmd | 0x20;
1360            } else if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
1361                pcmd = pcmd | 0x28;
1362            } else if (getMode().equals(ProgrammingMode.REGISTERMODE)
1363                    || getMode().equals(ProgrammingMode.ADDRESSMODE)) {
1364                pcmd = pcmd | 0x10;
1365            } else {
1366                throw new jmri.ProgrammerException("mode not supported"); // NOI18N
1367            }
1368
1369            doWrite(CV, val, p, pcmd);
1370        }
1371    }
1372
1373    /**
1374     * Perform a write a CV via the Service Mode programmer.
1375     *
1376     * @param CV CV number
1377     * @param val value to write to the CV
1378     * @param p programmer
1379     * @param pcmd programming command
1380     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1381     */
1382    public void doWrite(int CV, int val, jmri.ProgListener p, int pcmd) throws jmri.ProgrammerException {
1383        log.debug("writeCV: {}", CV); // NOI18N
1384
1385        stopEndOfProgrammingTimer();  // still programming, so no longer waiting for power off
1386
1387        useProgrammer(p);
1388        _progRead = false;
1389        _progConfirm = false;
1390        // set commandPending state
1391        progState = 1;
1392
1393        // format and send message
1394        startShortTimer();
1395        tc.sendLocoNetMessage(progTaskStart(pcmd, val, CV, true));
1396    }
1397
1398    /**
1399     * Confirm a CV via the OpsMode programmer.
1400     *
1401     * @param CVname a String containing the CV name
1402     * @param val expected value
1403     * @param p programmer
1404     * @param addr address of loco to write to
1405     * @param longAddr true if addr is a long address
1406     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1407     */
1408    public void confirmCVOpsMode(String CVname, int val, jmri.ProgListener p,
1409            int addr, boolean longAddr) throws jmri.ProgrammerException {
1410        int CV = Integer.parseInt(CVname);
1411        lopsa = addr & 0x7f;
1412        hopsa = (addr / 128) & 0x7f;
1413        mServiceMode = false;
1414        doConfirm(CV, val, p, 0x2F);  // although LPE implies 0x2C, 0x2F is observed
1415    }
1416
1417    /**
1418     * Confirm a CV via the Service Mode programmer.
1419     *
1420     * @param CVname a String containing the CV name
1421     * @param val expected value
1422     * @param p programmer
1423     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1424     */
1425    @Override
1426    public void confirmCV(String CVname, int val, jmri.ProgListener p) throws jmri.ProgrammerException {
1427        int CV = Integer.parseInt(CVname);
1428        lopsa = 0;
1429        hopsa = 0;
1430        mServiceMode = true;
1431        if (getMode().equals(csOpSwProgrammingMode)) {
1432            log.debug("cvOpSw mode!");
1433            //handle Command Station OpSw programming here
1434            String[] parts = CVname.split("\\.");
1435            if ((parts[0].equals("csOpSw")) && (parts.length==2)) {
1436                if (csOpSwAccessor == null) {
1437                    csOpSwAccessor = new CsOpSwAccess(adaptermemo, p);
1438                } else {
1439                    csOpSwAccessor.setProgrammerListener(p);
1440                }
1441                // perform the CsOpSwMode read access
1442                log.debug("going to try the opsw access");
1443                csOpSwAccessor.readCsOpSw(CVname, p);
1444                return;
1445            } else {
1446                log.warn("rejecting the cs opsw access account unsupported CV name format");
1447                // unsupported format in "cv" name.  Signal an error.
1448                notifyProgListenerEnd(p, 1, ProgListener.SequenceError);
1449                return;
1450            }
1451        }
1452
1453        // parse the programming command
1454        int pcmd = 0x03;       // LPE implies 0x00, but 0x03 is observed
1455        if (getMode().equals(ProgrammingMode.PAGEMODE)) {
1456            pcmd = pcmd | 0x20;
1457        } else if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
1458            pcmd = pcmd | 0x28;
1459        } else if (getMode().equals(ProgrammingMode.REGISTERMODE)
1460                || getMode().equals(ProgrammingMode.ADDRESSMODE)) {
1461            pcmd = pcmd | 0x10;
1462        } else {
1463            throw new jmri.ProgrammerException("mode not supported"); // NOI18N
1464        }
1465
1466        doConfirm(CV, val, p, pcmd);
1467    }
1468
1469    /**
1470     * Perform a confirm operation of a CV via the Service Mode programmer.
1471     *
1472     * @param CV the CV number
1473     * @param val expected value
1474     * @param p programmer
1475     * @param pcmd programming command
1476     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1477     */
1478    public void doConfirm(int CV, int val, ProgListener p,
1479            int pcmd) throws jmri.ProgrammerException {
1480
1481        log.debug("confirmCV: {}, val: {}", CV, val); // NOI18N
1482
1483        stopEndOfProgrammingTimer();  // still programming, so no longer waiting for power off
1484
1485        useProgrammer(p);
1486        _progRead = false;
1487        _progConfirm = true;
1488        _confirmVal = val;
1489
1490        // set commandPending state
1491        progState = 1;
1492
1493        // format and send message
1494        startShortTimer();
1495        tc.sendLocoNetMessage(progTaskStart(pcmd, val, CV, false));
1496    }
1497
1498    int hopsa; // high address for CV read/write
1499    int lopsa; // low address for CV read/write
1500
1501    CsOpSwAccess csOpSwAccessor;
1502
1503    @Override
1504    public void readCV(String cvNum, jmri.ProgListener p) throws jmri.ProgrammerException {
1505        readCV(cvNum, p, 0);
1506    }
1507
1508    /**
1509     * Read a CV via the OpsMode programmer.
1510     *
1511     * @param cvNum a String containing the CV number
1512     * @param p programmer
1513     * @param startVal initial "guess" for value of CV, can improve speed if used
1514     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1515     */
1516    @Override
1517    public void readCV(String cvNum, jmri.ProgListener p, int startVal) throws jmri.ProgrammerException {
1518        log.debug("readCV(string): cvNum={}, startVal={}, mode={}", cvNum, startVal, getMode());
1519        if (getMode().equals(csOpSwProgrammingMode)) {
1520            log.debug("cvOpSw mode!");
1521            //handle Command Station OpSw programming here
1522            String[] parts = cvNum.split("\\.");
1523            if ((parts[0].equals("csOpSw")) && (parts.length==2)) {
1524                if (csOpSwAccessor == null) {
1525                    csOpSwAccessor = new CsOpSwAccess(adaptermemo, p);
1526                } else {
1527                    csOpSwAccessor.setProgrammerListener(p);
1528                }
1529                // perform the CsOpSwMode read access
1530                log.debug("going to try the opsw access");
1531                csOpSwAccessor.readCsOpSw(cvNum, p);
1532                return;
1533
1534            } else {
1535                log.warn("rejecting the cs opsw access account unsupported CV name format");
1536                // unsupported format in "cv" name.  Signal an error.
1537                notifyProgListenerEnd(p, 1, ProgListener.SequenceError);
1538                return;
1539
1540            }
1541        } else {
1542            // regular integer address for DCC form
1543            int CV = Integer.parseInt(cvNum);
1544
1545            lopsa = 0;
1546            hopsa = 0;
1547            mServiceMode = true;
1548            // parse the programming command
1549            int pcmd = 0x03;       // LPE implies 0x00, but 0x03 is observed
1550            if (getMode().equals(ProgrammingMode.PAGEMODE)) {
1551                pcmd = pcmd | 0x20;
1552            } else if (getMode().equals(ProgrammingMode.DIRECTBYTEMODE)) {
1553                pcmd = pcmd | 0x28;
1554            } else if (getMode().equals(ProgrammingMode.REGISTERMODE)
1555                    || getMode().equals(ProgrammingMode.ADDRESSMODE)) {
1556                pcmd = pcmd | 0x10;
1557            } else {
1558                throw new jmri.ProgrammerException("mode not supported"); // NOI18N
1559            }
1560
1561            doRead(CV, p, pcmd, startVal);
1562
1563        }
1564    }
1565
1566    /**
1567     * Invoked by LnOpsModeProgrammer to start an ops-mode read operation.
1568     *
1569     * @param CVname       Which CV to read
1570     * @param p        Who to notify on complete
1571     * @param addr     Address of the locomotive
1572     * @param longAddr true if a long address, false if short address
1573     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1574     */
1575    public void readCVOpsMode(String CVname, jmri.ProgListener p, int addr, boolean longAddr) throws jmri.ProgrammerException {
1576        final int CV = Integer.parseInt(CVname);
1577        lopsa = addr & 0x7f;
1578        hopsa = (addr / 128) & 0x7f;
1579        mServiceMode = false;
1580        doRead(CV, p, 0x2F, 0);  // although LPE implies 0x2C, 0x2F is observed
1581    }
1582
1583    /**
1584     * Perform a CV Read.
1585     *
1586     * @param CV the CV number
1587     * @param p programmer
1588     * @param progByte programming command
1589     * @param startVal initial "guess" for value of CV, can improve speed if used
1590     * @throws jmri.ProgrammerException if an unsupported programming mode is exercised
1591     */
1592    void doRead(int CV, jmri.ProgListener p, int progByte, int startVal) throws jmri.ProgrammerException {
1593
1594        log.debug("readCV: {} with startVal: {}", CV, startVal); // NOI18N
1595
1596        stopEndOfProgrammingTimer();  // still programming, so no longer waiting for power off
1597
1598        useProgrammer(p);
1599        _progRead = true;
1600        _progConfirm = false;
1601        // set commandPending state
1602        progState = 1;
1603
1604        // format and send message
1605        startShortTimer();
1606//        tc.sendLocoNetMessage(progTaskStart(progByte, 0, CV, false));
1607        tc.sendLocoNetMessage(progTaskStart(progByte, startVal, CV, false));
1608    }
1609
1610    private jmri.ProgListener _usingProgrammer = null;
1611
1612    // internal method to remember who's using the programmer
1613    protected void useProgrammer(jmri.ProgListener p) throws jmri.ProgrammerException {
1614        // test for only one!
1615        if (_usingProgrammer != null && _usingProgrammer != p) {
1616
1617            log.info("programmer already in use by {}", _usingProgrammer); // NOI18N
1618
1619            throw new jmri.ProgrammerException("programmer in use"); // NOI18N
1620        } else {
1621            _usingProgrammer = p;
1622            return;
1623        }
1624    }
1625
1626    /**
1627     * Internal method to create the LocoNetMessage for programmer task start.
1628     *
1629     * @param pcmd programmer command
1630     * @param val value to be used
1631     * @param cvnum CV number
1632     * @param write true if write, else false
1633     * @return a LocoNet message containing a programming task start operation
1634     */
1635    protected LocoNetMessage progTaskStart(int pcmd, int val, int cvnum, boolean write) {
1636
1637        int addr = cvnum - 1;    // cvnum is in human readable form; addr is what's sent over LocoNet
1638
1639        LocoNetMessage m = new LocoNetMessage(14);
1640
1641        m.setOpCode(LnConstants.OPC_WR_SL_DATA);
1642        m.setElement(1, 0x0E);
1643        m.setElement(2, LnConstants.PRG_SLOT);
1644
1645        m.setElement(3, pcmd);
1646
1647        // set zero, then HOPSA, LOPSA, TRK
1648        m.setElement(4, 0);
1649        m.setElement(5, hopsa);
1650        m.setElement(6, lopsa);
1651        m.setElement(7, 0);  // TRK was 0, then 7 for PR2, now back to zero
1652
1653        // store address in CVH, CVL. Note CVH format is truely wierd...
1654        m.setElement(8, ((addr & 0x300)>>4) | ((addr & 0x80) >> 7) | ((val & 0x80) >> 6));
1655        m.setElement(9, addr & 0x7F);
1656
1657        // store low bits of CV value
1658        m.setElement(10, val & 0x7F);
1659
1660        // throttle ID
1661        m.setElement(11, 0x7F);
1662        m.setElement(12, 0x7F);
1663        return m;
1664    }
1665
1666    /**
1667     * Internal method to notify of the final result.
1668     *
1669     * @param value  The cv value to be returned
1670     * @param status The error code, if any
1671     */
1672    protected void notifyProgListenerEnd(int value, int status) {
1673        log.debug("  notifyProgListenerEnd with {}, {} and _usingProgrammer = {}", value, status, _usingProgrammer); // NOI18N
1674        // (re)start power timer
1675        restartEndOfProgrammingTimer();
1676        // and send the reply
1677        ProgListener p = _usingProgrammer;
1678        _usingProgrammer = null;
1679        if (p != null) {
1680            sendProgrammingReply(p, value, status);
1681        }
1682    }
1683
1684    /**
1685     * Internal method to notify of the LACK result. This is a separate routine
1686     * from nPLRead in case we need to handle something later.
1687     *
1688     * @param status The error code, if any
1689     */
1690    protected void notifyProgListenerLack(int status) {
1691        // (re)start power timer
1692        restartEndOfProgrammingTimer();
1693        // and send the reply
1694        sendProgrammingReply(_usingProgrammer, -1, status);
1695        _usingProgrammer = null;
1696    }
1697
1698    /**
1699     * Internal routine to forward a programming reply. This is delayed to
1700     * prevent overruns of the command station.
1701     *
1702     * @param p a ProgListener object
1703     * @param value  the value to return
1704     * @param status The error code, if any
1705     */
1706    protected void sendProgrammingReply(ProgListener p, int value, int status) {
1707        int delay = serviceModeReplyDelay;  // value in service mode
1708        if (!mServiceMode) {
1709            delay = opsModeReplyDelay;  // value in ops mode
1710        }
1711
1712        // delay and run on GUI thread
1713        javax.swing.Timer timer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() {
1714            @Override
1715            public void actionPerformed(java.awt.event.ActionEvent e) {
1716                notifyProgListenerEnd(p, value, status);
1717            }
1718        });
1719        timer.setInitialDelay(delay);
1720        timer.setRepeats(false);
1721        timer.start();
1722    }
1723
1724    /**
1725     * Internal routine to stop end-of-programming timer, as another programming
1726     * operation has happened.
1727     */
1728    protected void stopEndOfProgrammingTimer() {
1729        if (mPowerTimer != null) {
1730            mPowerTimer.stop();
1731        }
1732    }
1733
1734    /**
1735     * Internal routine to handle timer restart if needed to restore power. This
1736     * is only needed in service mode.
1737     */
1738    protected void restartEndOfProgrammingTimer() {
1739        final int delay = 10000;
1740        if (mProgEndSequence) {
1741            if (mPowerTimer == null) {
1742                mPowerTimer = new javax.swing.Timer(delay, new java.awt.event.ActionListener() {
1743                    @Override
1744                    public void actionPerformed(java.awt.event.ActionEvent e) {
1745                        doEndOfProgramming();
1746                    }
1747                });
1748            }
1749            mPowerTimer.stop();
1750            mPowerTimer.setInitialDelay(delay);
1751            mPowerTimer.setRepeats(false);
1752            mPowerTimer.start();
1753        }
1754    }
1755
1756    /**
1757     * Internal routine to handle a programming timeout by turning power off.
1758     */
1759    synchronized protected void doEndOfProgramming() {
1760        if (progState == 0) {
1761             if ( mServiceMode ) {
1762                // finished service-track programming, time to power on
1763                log.debug("end service-mode programming: turn power on"); // NOI18N
1764                try {
1765                    jmri.InstanceManager.getDefault(jmri.PowerManager.class).setPower(jmri.PowerManager.ON);
1766                } catch (jmri.JmriException e) {
1767                    log.error("exception during power on at end of programming", e); // NOI18N
1768                }
1769            } else {
1770                log.debug("end ops-mode programming: no power change"); // NOI18N
1771            }
1772        }
1773    }
1774
1775    javax.swing.Timer mPowerTimer = null;
1776
1777    ReadAllSlots_Helper _rAS = null;
1778
1779    /**
1780     * Start the process of checking each slot for contents.
1781     * <p>
1782     * This is not invoked by this class, but can be invoked from elsewhere to
1783     * start the process of scanning all slots to update their contents.
1784     *
1785     * If an instance is already running then the request is ignored
1786     *
1787     * @param inputSlotMap array of from to pairs
1788     * @param interval ms between slt rds
1789     */
1790    synchronized public void update(List<SlotMapEntry> inputSlotMap, int interval) {
1791        if (_rAS == null) {
1792            _rAS = new ReadAllSlots_Helper(  inputSlotMap, interval);
1793            jmri.util.ThreadingUtil.newThread(_rAS, "Read All Slots ").start();
1794        } else {
1795            if (!_rAS.isRunning()) {
1796                jmri.util.ThreadingUtil.newThread(_rAS, "Read All Slots ").start();
1797            }
1798        }
1799    }
1800
1801    /**
1802     * Checks slotNum valid for slot map
1803     *
1804     * @param slotNum the slot number
1805     * @return true if it is
1806     */
1807    private boolean validateSlotNumber(int slotNum) {
1808        for (SlotMapEntry item : slotMap) {
1809            if (slotNum >= item.getFrom() && slotNum <= item.getTo()) {
1810                return true;
1811            }
1812        }
1813        return false;
1814    }
1815
1816    public void update() {
1817        update(slotMap, slotScanInterval);
1818    }
1819
1820    /**
1821     * Send a message requesting the data from a particular slot.
1822     *
1823     * @param slot Slot number
1824     */
1825    public void sendReadSlot(int slot) {
1826        LocoNetMessage m = new LocoNetMessage(4);
1827        m.setOpCode(LnConstants.OPC_RQ_SL_DATA);
1828        m.setElement(1, slot & 0x7F);
1829        // one is always short
1830        // THis gets a little akward, slots 121 thru 127 incl. seem to always old slots.
1831        // All slots gt 127 are always expanded format.
1832        if ( slot > 127 || ( ( slot > 0 && slot < 121 ) && loconetProtocol == LnConstants.LOCONETPROTOCOL_TWO ) ) {
1833            m.setElement(2, (slot / 128 ) & 0b00000111 | 0x40 );
1834        }
1835        tc.sendLocoNetMessage(m);
1836    }
1837
1838    protected int nextReadSlot = 0;
1839
1840    /**
1841     * Continue the sequence of reading all slots.
1842     * @param toSlot index of the next slot to read
1843     * @param interval wait time before operation, milliseconds
1844     */
1845    synchronized protected void readNextSlot(int toSlot, int interval) {
1846        // send info request
1847        sendReadSlot(nextReadSlot++);
1848
1849        // schedule next read if needed
1850        if (nextReadSlot < toSlot) {
1851            javax.swing.Timer t = new javax.swing.Timer(interval, new java.awt.event.ActionListener() {
1852                @Override
1853                public void actionPerformed(java.awt.event.ActionEvent e) {
1854                    readNextSlot(toSlot,interval);
1855                }
1856            });
1857            t.setRepeats(false);
1858            t.start();
1859        }
1860    }
1861
1862    /**
1863     * Provide a snapshot of the slots in use.
1864     * <p>
1865     * Note that the count of "in-use" slots may be somewhat misleading,
1866     * as slots in the "common" state can be controlled and are occupying
1867     * a slot in a meaningful way.
1868     *
1869     * @return the count of in-use LocoNet slots
1870     */
1871    public int getInUseCount() {
1872        int result = 0;
1873        for (int i = 0; i <= 120; i++) {
1874            if (slot(i).slotStatus() == LnConstants.LOCO_IN_USE) {
1875                result++;
1876            }
1877        }
1878        return result;
1879    }
1880
1881    /**
1882     * Set the system connection memo.
1883     *
1884     * @param memo a LocoNetSystemConnectionMemo
1885     */
1886    public void setSystemConnectionMemo(LocoNetSystemConnectionMemo memo) {
1887        adaptermemo = memo;
1888    }
1889
1890    LocoNetSystemConnectionMemo adaptermemo;
1891
1892    /**
1893     * Get the "user name" for the slot manager connection, from the memo.
1894     *
1895     * @return the connection's user name or "LocoNet" if the memo
1896     * does not exist
1897     */
1898    @Override
1899    public String getUserName() {
1900        if (adaptermemo == null) {
1901            return "LocoNet"; // NOI18N
1902        }
1903        return adaptermemo.getUserName();
1904    }
1905
1906    /**
1907     * Return the memo "system prefix".
1908     *
1909     * @return the system prefix or "L" if the memo
1910     * does not exist
1911     */
1912    @Override
1913    public String getSystemPrefix() {
1914        if (adaptermemo == null) {
1915            return "L";
1916        }
1917        return adaptermemo.getSystemPrefix();
1918    }
1919
1920    boolean transpondingAvailable = false;
1921    public void setTranspondingAvailable(boolean val) { transpondingAvailable = val; }
1922    public boolean getTranspondingAvailable() { return transpondingAvailable; }
1923
1924    /**
1925     *
1926     * @param val If false then we only use protocol one.
1927     */
1928    public void setLoconetProtocolAutoDetect(boolean val) {
1929        if (!val) {
1930            loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE;
1931            // slots would have been created with unknown for auto detect
1932            for( int ix = 0; ix < 128; ix++ ) {
1933                slot(ix).setProtocol(loconetProtocol);
1934            }
1935        }
1936    }
1937
1938    /**
1939     * Get the memo.
1940     *
1941     * @return the memo
1942     */
1943    public LocoNetSystemConnectionMemo getSystemConnectionMemo() {
1944        return adaptermemo;
1945    }
1946
1947    /**
1948     * Dispose of this by stopped it's ongoing actions
1949     */
1950    @Override
1951    public void dispose() {
1952        if (staleSlotCheckTimer != null) {
1953            staleSlotCheckTimer.stop();
1954        }
1955    }
1956
1957    // initialize logging
1958    private final static Logger log = LoggerFactory.getLogger(SlotManager.class);
1959
1960    // Read all slots
1961    class ReadAllSlots_Helper implements Runnable {
1962
1963        ReadAllSlots_Helper(List<SlotMapEntry> inputSlotMap, int interval) {
1964            this.interval = interval;
1965        }
1966
1967        private int interval;
1968        private boolean abort = false;
1969        private boolean isRunning = false;
1970
1971        /**
1972         * Aborts current run
1973         */
1974        public void setAbort() {
1975            abort = true;
1976        }
1977
1978        /**
1979         * Gets the current stae of the run.
1980         * @return true if running
1981         */
1982        public boolean isRunning() {
1983            return isRunning;
1984        }
1985
1986        @Override
1987        public void run() {
1988            abort = false;
1989            isRunning = true;
1990            // read all slots that are not of unknown type
1991            for (int slot = 0; slot < getNumSlots() && !abort; slot++) {
1992                if (_slots[slot].getSlotType() != SlotType.UNKNOWN) {
1993                    sendReadSlot(slot);
1994                    try {
1995                        Thread.sleep(this.interval);
1996                    } catch (Exception ex) {
1997                        // just abort
1998                        abort = true;
1999                        break;
2000                    }
2001                }
2002            }
2003            isRunning = false;
2004        }
2005    }
2006
2007}