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
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        switch (slot) {
528            case 250:
529                // slot info if we have serial, the serial number in this slot
530                // does not indicate whether in booster or cs mode.
531                if (slot248CommandStationSerial == ((m.getElement(19) & 0x3F) * 128) + m.getElement(18)) {
532                    slot250InUseSlots = (m.getElement(4) + ((m.getElement(5) & 0x03) * 128));
533                    slot250IdleSlots = (m.getElement(6) + ((m.getElement(7) & 0x03) * 128));
534                    slot250FreeSlots = (m.getElement(8) + ((m.getElement(9) & 0x03) * 128));
535                }
536                break;
537            case 248:
538                // Base HW Information
539                // If a CS in CS mode then byte 19 bit 6 in on. else its in
540                // booster mode
541                // The device type is in byte 14
542                if ((m.getElement(19) & 0x40) == 0x40) {
543                    slot248CommandStationSerial = ((m.getElement(19) & 0x3F) * 128) + m.getElement(18);
544                    slot248CommandStationType = m.getElement(14);
545                }
546                break;
547            default:
548        }
549    }
550
551    /*
552     * If protocol not yet established use slot status for protocol support
553     * System slots , except zero, do not have this info
554     */
555    void checkLoconetProtocol(LocoNetMessage m, int slot) {
556        // detect protocol if not yet set
557        if (getLoconetProtocol() == LnConstants.LOCONETPROTOCOL_UNKNOWN) {
558            if (_slots[slot].getSlotType() != SlotType.SYSTEM || slot == 0) {
559                if ((m.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA && m.getNumDataElements() == 21) ||
560                        (m.getOpCode() == LnConstants.OPC_SL_RD_DATA)) {
561                    if ((m.getElement(7) & 0b01000000) == 0b01000000) {
562                        log.info("Setting protocol Loconet 2");
563                        setLoconet2Supported(LnConstants.LOCONETPROTOCOL_TWO);
564                    } else {
565                        log.info("Setting protocol Loconet 1");
566                        setLoconet2Supported(LnConstants.LOCONETPROTOCOL_ONE);
567                    }
568                }
569            }
570        }
571    }
572
573    /**
574     * Checks a LocoNet message to see if it encodes a DCC "direct function" packet.
575     *
576     * @param m  a LocoNet Message
577     * @return the loco address if the LocoNet message encodes a "direct function" packet,
578     * else returns -1
579     */
580    int getDirectFunctionAddress(LocoNetMessage m) {
581        if (m.getOpCode() != LnConstants.OPC_IMM_PACKET) {
582            return -1;
583        }
584        if (m.getElement(1) != 0x0B) {
585            return -1;
586        }
587        if (m.getElement(2) != 0x7F) {
588            return -1;
589        }
590        // Direct packet, check length
591        if ((m.getElement(3) & 0x70) < 0x20) {
592            return -1;
593        }
594        int addr = -1;
595        // check long address
596        if ((m.getElement(4) & 0x01) == 0) { //bit 7=0 short
597            addr = (m.getElement(5) & 0xFF);
598            if ((m.getElement(4) & 0x01) != 0) {
599                addr += 128;  // and high bit
600            }
601        } else if ((m.getElement(5) & 0x40) == 0x40) { // bit 7 = 1 if bit 6 = 1 then long
602            addr = (m.getElement(5) & 0x3F) * 256 + (m.getElement(6) & 0xFF);
603            if ((m.getElement(4) & 0x02) != 0) {
604                addr += 128;  // and high bit
605            }
606        } else { // accessory decoder or extended accessory decoder
607            addr = (m.getElement(5) & 0x3F);
608        }
609        return addr;
610    }
611
612    /**
613     * Extracts a DCC "direct packet" from a LocoNet message, if possible.
614     * <p>
615     * if this is a direct DCC packet, return as one long
616     * else return -1. Packet does not include address bytes.
617     *
618     * @param m a LocoNet message to be inspected
619     * @return an integer containing the bytes of the DCC packet, except the address bytes.
620     */
621    int getDirectDccPacket(LocoNetMessage m) {
622        if (m.getOpCode() != LnConstants.OPC_IMM_PACKET) {
623            return -1;
624        }
625        if (m.getElement(1) != 0x0B) {
626            return -1;
627        }
628        if (m.getElement(2) != 0x7F) {
629            return -1;
630        }
631        // Direct packet, check length
632        if ((m.getElement(3) & 0x70) < 0x20) {
633            return -1;
634        }
635        int result = 0;
636        int n = (m.getElement(3) & 0xF0) / 16;
637        int start;
638        int high = m.getElement(4);
639        // check long or short address
640        if ((m.getElement(4) & 0x01) == 1 && (m.getElement(5) & 0x40) == 0x40 ) {  //long address bit 7 im1 = 1 and bit6 im1 = 1
641            start = 7;
642            high = high >> 2;
643            n = n - 2;
644         } else {  //short or accessory
645            start = 6;
646            high = high >> 1;
647            n = n - 1;
648        }
649        // get result
650        for (int i = 0; i < n; i++) {
651            result = result * 256 + (m.getElement(start + i) & 0x7F);
652            if ((high & 0x01) != 0) {
653                result += 128;
654            }
655            high = high >> 1;
656        }
657        return result;
658    }
659
660    /**
661     * Determines if a LocoNet message encodes a direct request to control
662     * DCC functions F9 thru F28
663     *
664     * @param m the LocoNet message to be evaluated
665     * @return true if the message is an external DCC packet request for F9-F28,
666     *      else false.
667     */
668    boolean isExtFunctionMessage(LocoNetMessage m) {
669        int pkt = getDirectDccPacket(m);
670        if (pkt < 0) {
671            return false;
672        }
673        // check F9-12
674        if ((pkt & 0xFFFFFF0) == 0xA0) {
675            return true;
676        }
677        // check F13-28
678        if ((pkt & 0xFFFFFE00) == 0xDE00) {
679            return true;
680        }
681        return false;
682    }
683
684    /**
685     * Extracts the LocoNet slot number from a LocoNet message, if possible.
686     * <p>
687     * Find the slot number that a message references
688     * <p>
689     * This routine only looks for explicit slot references; it does not, for example,
690     * identify a loco address in the message and then work thru the slots to find a
691     * slot which references that loco address.
692     *
693     * @param m LocoNet Message to be inspected
694     * @return an integer representing the slot number encoded in the LocoNet
695     *          message, or -1 if the message does not contain a slot reference
696     */
697    public int findSlotFromMessage(LocoNetMessage m) {
698
699        int i = -1;  // find the slot index in the message and store here
700
701        // decode the specific message type and hence slot number
702        switch (m.getOpCode()) {
703            case LnConstants.OPC_WR_SL_DATA:
704            case LnConstants.OPC_SL_RD_DATA:
705                i = m.getElement(2);
706                break;
707            case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL:
708                if ( m.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN) {
709                    i = m.getElement(2);
710                    break;
711                }
712                i = ( (m.getElement(1) & 0x03 ) *128) + m.getElement(2);
713                break;
714            case LnConstants.OPC_LOCO_DIRF:
715            case LnConstants.OPC_LOCO_SND:
716            case LnConstants.OPC_LOCO_SPD:
717            case LnConstants.OPC_SLOT_STAT1:
718            case LnConstants.OPC_LINK_SLOTS:
719            case LnConstants.OPC_UNLINK_SLOTS:
720                i = m.getElement(1);
721                break;
722
723            case LnConstants.OPC_MOVE_SLOTS:  // No follow on for some moves
724                if (m.getElement(1) != 0) {
725                    i = m.getElement(1);
726                    return i;
727                }
728                break;
729            case LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR:
730                i = ( (m.getElement(1) & 0x03 ) *128) + m.getElement(2);
731                break;
732            case LnConstants.OPC_EXP_RD_SL_DATA:
733            case LnConstants.OPC_EXP_WR_SL_DATA:
734                //only certain lengths get passed to slot
735                if (m.getElement(1) == 21) {
736                    i = ( (m.getElement(2) & 0x03 ) *128) + m.getElement(3);
737                }
738                return i;
739            default:
740                // nothing here for us
741                return i;
742        }
743        // break gets to here
744        return i;
745    }
746
747    /**
748     * Check CV programming LONG_ACK message byte 1
749     * <p>
750     * The following methods are for parsing LACK as response to CV programming.
751     * It is divided into numerous small methods so that each bit can be
752     * overridden for special parsing for individual command station types.
753     *
754     * @param byte1 from the LocoNet message
755     * @return true if byte1 encodes a response to a OPC_SL_WRITE or an
756     *          Expanded Slot Write
757     */
758    protected boolean checkLackByte1(int byte1) {
759        if ((byte1 & 0xEF) == 0x6F) {
760            return true;
761        } else {
762            return false;
763        }
764    }
765
766    /**
767     * Checks the status byte of an OPC_LONG_ACK when performing CV programming
768     * operations.
769     *
770     * @param byte2 status byte
771     * @return True if status byte indicates acceptance of the command, else false.
772     */
773    protected boolean checkLackTaskAccepted(int byte2) {
774        if (byte2 == 1 // task accepted
775                || byte2 == 0x23 || byte2 == 0x2B || byte2 == 0x6B // added as DCS51 fix
776                // deliberately ignoring 0x7F varient, see okToIgnoreLack
777            ) {
778            return true;
779        } else {
780            return false;
781        }
782    }
783
784    /**
785     * Checks the OPC_LONG_ACK status byte response to a programming
786     * operation.
787     *
788     * @param byte2 from the OPC_LONG_ACK message
789     * @return true if the programmer returned "busy" else false
790     */
791    protected boolean checkLackProgrammerBusy(int byte2) {
792        if (byte2 == 0) {
793            return true;
794        } else {
795            return false;
796        }
797    }
798
799    /**
800     * Checks the OPC_LONG_ACK status byte response to a programming
801     * operation to see if the programmer accepted the operation "blindly".
802     *
803     * @param byte2 from the OPC_LONG_ACK message
804     * @return true if the programmer indicated a "blind operation", else false
805     */
806    protected boolean checkLackAcceptedBlind(int byte2) {
807        if (byte2 == 0x40) {
808            return true;
809        } else {
810            return false;
811        }
812    }
813
814    /**
815     * Some LACKs with specific OPC_LONG_ACK status byte values can just be ignored.
816     *
817     * @param byte2 from the OPC_LONG_ACK message
818     * @return true if this form of LACK can be ignored without a warning message
819     */
820    protected boolean okToIgnoreLack(int byte2) {
821        if (byte2 == 0x7F ) {
822            return true;
823        } else {
824            return false;
825        }
826    }
827
828    private boolean acceptAnyLACK = false;
829    /**
830     * Indicate that the command station LONG_ACK response details can be ignored
831     * for this operation.  Typically this is used when accessing Loconet-attached boards.
832     */
833    public final void setAcceptAnyLACK() {
834        acceptAnyLACK = true;
835    }
836
837    /**
838     * Handles OPC_LONG_ACK replies to programming slot operations.
839     *
840     * LACK 0x6D00 which requests a retransmission is handled
841     * separately in the message(..) method.
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    public synchronized void update(List<SlotMapEntry> inputSlotMap, int interval) {
1791        if (_rAS == null) {
1792            _rAS = new ReadAllSlots_Helper(  inputSlotMap, interval);
1793            jmri.util.ThreadingUtil.newThread(_rAS, getUserName() + READ_ALL_SLOTS_THREADNAME).start();
1794        } else {
1795            if (!_rAS.isRunning()) {
1796                jmri.util.ThreadingUtil.newThread(_rAS, getUserName() + READ_ALL_SLOTS_THREADNAME).start();
1797            }
1798        }
1799    }
1800
1801    /**
1802     * String with name for Read all slots thread.
1803     * Requires getUserName prepending.
1804     */
1805    public static final String READ_ALL_SLOTS_THREADNAME = " Read All Slots ";
1806
1807    /**
1808     * Checks slotNum valid for slot map
1809     *
1810     * @param slotNum the slot number
1811     * @return true if it is
1812     */
1813    private boolean validateSlotNumber(int slotNum) {
1814        for (SlotMapEntry item : slotMap) {
1815            if (slotNum >= item.getFrom() && slotNum <= item.getTo()) {
1816                return true;
1817            }
1818        }
1819        return false;
1820    }
1821
1822    public void update() {
1823        update(slotMap, slotScanInterval);
1824    }
1825
1826    /**
1827     * Send a message requesting the data from a particular slot.
1828     *
1829     * @param slot Slot number
1830     */
1831    public void sendReadSlot(int slot) {
1832        LocoNetMessage m = new LocoNetMessage(4);
1833        m.setOpCode(LnConstants.OPC_RQ_SL_DATA);
1834        m.setElement(1, slot & 0x7F);
1835        // one is always short
1836        // THis gets a little akward, slots 121 thru 127 incl. seem to always old slots.
1837        // All slots gt 127 are always expanded format.
1838        if ( slot > 127 || ( ( slot > 0 && slot < 121 ) && loconetProtocol == LnConstants.LOCONETPROTOCOL_TWO ) ) {
1839            m.setElement(2, (slot / 128 ) & 0b00000111 | 0x40 );
1840        }
1841        tc.sendLocoNetMessage(m);
1842    }
1843
1844    protected int nextReadSlot = 0;
1845
1846    /**
1847     * Continue the sequence of reading all slots.
1848     * @param toSlot index of the next slot to read
1849     * @param interval wait time before operation, milliseconds
1850     */
1851    synchronized protected void readNextSlot(int toSlot, int interval) {
1852        // send info request
1853        sendReadSlot(nextReadSlot++);
1854
1855        // schedule next read if needed
1856        if (nextReadSlot < toSlot) {
1857            javax.swing.Timer t = new javax.swing.Timer(interval, new java.awt.event.ActionListener() {
1858                @Override
1859                public void actionPerformed(java.awt.event.ActionEvent e) {
1860                    readNextSlot(toSlot,interval);
1861                }
1862            });
1863            t.setRepeats(false);
1864            t.start();
1865        }
1866    }
1867
1868    /**
1869     * Provide a snapshot of the slots in use.
1870     * <p>
1871     * Note that the count of "in-use" slots may be somewhat misleading,
1872     * as slots in the "common" state can be controlled and are occupying
1873     * a slot in a meaningful way.
1874     *
1875     * @return the count of in-use LocoNet slots
1876     */
1877    public int getInUseCount() {
1878        int result = 0;
1879        for (int i = 0; i <= 120; i++) {
1880            if (slot(i).slotStatus() == LnConstants.LOCO_IN_USE) {
1881                result++;
1882            }
1883        }
1884        return result;
1885    }
1886
1887    /**
1888     * Set the system connection memo.
1889     *
1890     * @param memo a LocoNetSystemConnectionMemo
1891     */
1892    public void setSystemConnectionMemo(LocoNetSystemConnectionMemo memo) {
1893        adaptermemo = memo;
1894    }
1895
1896    LocoNetSystemConnectionMemo adaptermemo;
1897
1898    /**
1899     * Get the "user name" for the slot manager connection, from the memo.
1900     *
1901     * @return the connection's user name or "LocoNet" if the memo
1902     * does not exist
1903     */
1904    @Override
1905    public String getUserName() {
1906        if (adaptermemo == null) {
1907            return "LocoNet"; // NOI18N
1908        }
1909        return adaptermemo.getUserName();
1910    }
1911
1912    /**
1913     * Return the memo "system prefix".
1914     *
1915     * @return the system prefix or "L" if the memo
1916     * does not exist
1917     */
1918    @Override
1919    public String getSystemPrefix() {
1920        if (adaptermemo == null) {
1921            return "L";
1922        }
1923        return adaptermemo.getSystemPrefix();
1924    }
1925
1926    boolean transpondingAvailable = false;
1927    public void setTranspondingAvailable(boolean val) { transpondingAvailable = val; }
1928    public boolean getTranspondingAvailable() { return transpondingAvailable; }
1929
1930    /**
1931     *
1932     * @param val If false then we only use protocol one.
1933     */
1934    public void setLoconetProtocolAutoDetect(boolean val) {
1935        if (!val) {
1936            loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE;
1937            // slots would have been created with unknown for auto detect
1938            for( int ix = 0; ix < 128; ix++ ) {
1939                slot(ix).setProtocol(loconetProtocol);
1940            }
1941        }
1942    }
1943
1944    /**
1945     * Get the memo.
1946     *
1947     * @return the memo
1948     */
1949    public LocoNetSystemConnectionMemo getSystemConnectionMemo() {
1950        return adaptermemo;
1951    }
1952
1953    /**
1954     * Dispose of this by stopped it's ongoing actions
1955     */
1956    @Override
1957    public void dispose() {
1958        if (staleSlotCheckTimer != null) {
1959            staleSlotCheckTimer.stop();
1960        }
1961        if ( _rAS != null ) {
1962            _rAS.setAbort();
1963        }
1964    }
1965
1966    // initialize logging
1967    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SlotManager.class);
1968
1969    // Read all slots
1970    class ReadAllSlots_Helper implements Runnable {
1971
1972        ReadAllSlots_Helper(List<SlotMapEntry> inputSlotMap, int interval) {
1973            this.interval = interval;
1974        }
1975
1976        private int interval;
1977        private boolean abort = false;
1978        private boolean isRunning = false;
1979
1980        /**
1981         * Aborts current run
1982         */
1983        public void setAbort() {
1984            abort = true;
1985        }
1986
1987        /**
1988         * Gets the current stae of the run.
1989         * @return true if running
1990         */
1991        public boolean isRunning() {
1992            return isRunning;
1993        }
1994
1995        @Override
1996        public void run() {
1997            abort = false;
1998            isRunning = true;
1999            // read all slots that are not of unknown type
2000            for (int slot = 0; slot < getNumSlots() && !abort; slot++) {
2001                if (_slots[slot].getSlotType() != SlotType.UNKNOWN) {
2002                    sendReadSlot(slot);
2003                    try {
2004                        Thread.sleep(this.interval);
2005                    } catch (Exception ex) {
2006                        // just abort
2007                        abort = true;
2008                        break;
2009                    }
2010                }
2011            }
2012            isRunning = false;
2013        }
2014    }
2015
2016}