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