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