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