001package jmri.jmrix.loconet;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import jmri.jmrix.loconet.SlotMapEntry.SlotType;
005
006import java.util.ArrayList;
007import java.util.List;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Represents the contents of a single slot in the LocoNet command station.
013 * <p>
014 * A SlotListener can be registered to hear of changes in this slot. All changes
015 * in values will result in notification.
016 * <p>
017 * Strictly speaking, functions 9 through 28 are not in the actual slot, but
018 * it's convenient to imagine there's an "extended slot" and keep track of them
019 * here. This is a partial implementation, though, because setting is still done
020 * directly in {@link LocoNetThrottle}. In particular, if this slot has not been
021 * read from the command station, the first message directly setting F9 through
022 * F28 will not have a place to store information. Instead, it will trigger a
023 * slot read, so the following messages will be properly handled.
024 * <p>
025 * Some of the message formats used in this class are Copyright Digitrax, Inc.
026 * and used with permission as part of the JMRI project. That permission does
027 * not extend to uses in other software products. If you wish to use this code,
028 * algorithm or these message formats outside of JMRI, please contact Digitrax
029 * Inc for separate permission.
030 *
031 * @author Bob Jacobsen Copyright (C) 2001
032 * @author Stephen Williams Copyright (C) 2008
033 * @author B. Milhaupt, Copyright (C) 2018, 2025
034 * @author S. Gigiel, Copyright (C) 2018
035 */
036public class LocoNetSlot {
037
038    // create a specific slot
039    /**
040     * Create a slot based solely on a slot number.  The remainder of the slot is
041     * left un-initialized.
042     *
043     * @param slotNum  slot number to be assigned to the new LocoNetSlot object
044     */
045    public LocoNetSlot(int slotNum) {
046        this(slotNum, LnConstants.LOCONETPROTOCOL_UNKNOWN);
047    }
048
049    /**
050     * Create a slot based solely on a slot number.  The remainder of the slot is
051     * left un-initialized.
052     *
053     * @param slotNum - slot number to be assigned to the new LocoNetSlot object
054     * @param inLoconetProtocol - can be 0 = unknown, 1 = version 1.1, 2 = Expandedslot
055     */
056    public LocoNetSlot(int slotNum, int inLoconetProtocol) {
057        this(slotNum, inLoconetProtocol, SlotType.LOCO);
058        if ((slotNum == 0) || (slotNum > 120 && slot < 128)
059                || (slotNum > 247 && slotNum < 257)
060                || (slotNum > 375 && slotNum < 385)) {
061            slotType = SlotType.SYSTEM;
062        } else {
063            slotType = SlotType.LOCO;
064        }
065    }
066
067    /**
068     * Create a slot , initialize slotnum, protocol and slot type
069     *
070     * @param slotNum - slot number to be assigned to the new LocoNetSlot object
071     * @param inLoconetProtocol - can be 0 = unknown, 1 = version 1.1, 2 = Expandedslot
072     * @param inSlotType - SLotType enum
073     */
074    public LocoNetSlot(int slotNum, int inLoconetProtocol, SlotType inSlotType) {
075        slot = slotNum;
076        loconetProtocol = inLoconetProtocol;
077        if (slotNum > 127 ) {
078            // has to be 2
079            loconetProtocol = LnConstants.LOCONETPROTOCOL_TWO;
080        }
081        slotType = inSlotType;
082    }
083
084
085    /**
086     * Creates a slot object based on the contents of a LocoNet message.
087     * The slot number is assumed to be found in byte 2 of the message if message is 0xE6 or bytes 2 and 3 for 0xE7
088     *
089     * @param l  a LocoNet message
090     * @throws LocoNetException if the slot does not have an easily-found
091     * slot number
092     */
093    public LocoNetSlot(LocoNetMessage l) throws LocoNetException {
094        // TODO: Consider removing, only used in testing.
095        // TODO: Consider limiting the types of LocoNet message which can be
096        // used to construct the object to only LocoNet slot write or slot
097        // report messages, since a LocoNetSlot object constructed from a LocoNet
098        // "speed" message or "dir/func" message does not give any other useful
099        // information for object initialization.
100        if ( l.getOpCode() == LnConstants.OPC_SL_RD_DATA || l.getOpCode() == LnConstants.OPC_WR_SL_DATA)  {
101            slot = l.getElement(2);
102            loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE;
103        } else if (l.getOpCode() == LnConstants.OPC_EXP_RD_SL_DATA || l.getOpCode() == LnConstants.OPC_EXP_WR_SL_DATA) {
104            slot = ( (l.getElement(2) & 0x03 ) *128) + l.getElement(3);
105            loconetProtocol = LnConstants.LOCONETPROTOCOL_TWO;
106        } else {
107           throw new LocoNetException("Invalid loconet message for setting up a slot");
108        }
109        setSlot(l);
110    }
111
112    // accessors to specific information
113    /**
114     * Returns the slot number which was either specified or inferred at object
115     * creation time.
116     *
117     * @return the slot number
118     */
119    public int getSlot() {
120        return slot;
121    }  // cannot modify the slot number once created
122
123    /**
124     * Set the Slot Type
125     * @param value enum for slottype
126     */
127    public void setSlotType(SlotType value) {
128        slotType = value;
129    }
130
131    /***
132     *
133     * @return true if this is a systems slot else false
134     */
135    public boolean isSystemSlot() {
136        return slotType == SlotType.SYSTEM;
137    }
138
139    /***
140     *
141     * @return true if this is a systems slot else false
142     */
143    public SlotType getSlotType() {
144         return slotType;
145     }
146
147    /**
148     *
149     * @return the protocol level support by the slot.
150     */
151    public int getProtocol() {
152        return loconetProtocol;
153    }
154
155    /**
156     * set the protocol to be used
157     * @param value one,two or unknown
158     */
159   protected void setProtocol(int value) {
160        loconetProtocol = value;
161   }
162
163    /**
164     * Get decoder mode.
165     *
166     * The decoder (operating) mode is taken from those bits in the slot's STAT
167     * byte which reflect the "speed steps" and "consisting" mode.  Note that
168     * the other bits from the STAT byte are not visible via this method.
169     * this
170     * <p>
171     * For slot numbers not normally associated with mobile decoders, these bits
172     * may have other meanings.
173     * <p>
174     * Possible values are
175     * {@link LnConstants#DEC_MODE_128A},
176     * {@link LnConstants#DEC_MODE_28A},
177     * {@link LnConstants#DEC_MODE_128},
178     * {@link LnConstants#DEC_MODE_14},
179     * {@link LnConstants#DEC_MODE_28TRI},
180     * {@link LnConstants#DEC_MODE_28}
181     *
182     * @return the encoded decoder operating mode.
183     */
184    public int decoderType() {
185        return stat & LnConstants.DEC_MODE_MASK;
186    }
187
188    /**
189     * Get slot status.
190     * <p>
191     * These bits are set based on the STAT byte as seen in LocoNet slot write and
192     * slot read messages.  These bits determine whether the command station is
193     * actively "refreshing" the loco's speed and direction information on the
194     * DCC track signal, and whether the slot is able to be re-assigned for use
195     * by another locomotive.
196     * <p>
197     * For slot numbers not normally associated with mobile decoders, these bits
198     * may have other meanings.
199     * <p>
200     * This returns only those bits of the slot's STAT byte which are related to
201     * the slot's "status".
202     * <p>
203     * Possible values are
204     * {@link LnConstants#LOCO_IN_USE},
205     * {@link LnConstants#LOCO_IDLE},
206     * {@link LnConstants#LOCO_COMMON},
207     * {@link LnConstants#LOCO_FREE}
208     * @return the slot status bits associated with the slot
209     */
210    public int slotStatus() {
211        return stat & LnConstants.LOCOSTAT_MASK;
212    }
213
214    /**
215     * Get secondary slot status.
216     * <p>
217     * These bits are set based on the STAT2 byte as seen in LocoNet slot write and
218     * slot read messages.  These bits determine how the command station interprets
219     * the "address" field of the slot.
220     * <p>
221     * For slot numbers not normally associated with mobile decoders, these bits
222     * may have other meanings.
223     * <p>
224     * This returns only those bits of the slot's STAT2 byte which are related to
225     * the slot's "secondary status".
226     *
227     * @return the slot secondary status bits associated with the slot
228     */
229
230    public int ss2() {
231        return ss2;
232    }
233
234    /**
235     * Get the state of the Acquire Throttle / slot.
236     * It is fully initialized if this is true.
237     * If it is false then any changes to its state may be lost.
238     * @return true
239     */
240    public boolean getIsInitilized() {
241        return isInitialized;
242    }
243
244    protected void setIsInitialized(boolean state) {
245        isInitialized = state;
246    }
247
248    /**
249     * Get consist status.
250     * <p>
251     * This returns only those bits of the slot's STAT byte which are related to
252     * the slot's "consisting status".
253     * <p>
254     * For slot numbers not normally associated with mobile decoders, these bits
255     * may have other meanings.
256     * <p>
257     * Possible values are
258     * {@link LnConstants#CONSIST_NO},
259     * {@link LnConstants#CONSIST_TOP},
260     * {@link LnConstants#CONSIST_MID},
261     * {@link LnConstants#CONSIST_SUB}
262     * @return the slot "consist status", with unrelated bits zeroed
263     */
264    public int consistStatus() {
265        return stat & LnConstants.CONSIST_MASK;
266    }
267
268    // direction and functions
269    /**
270     * Returns the direction of loco movement which applies when the slot's speed
271     * is set for movement.
272     * <p>
273     * For slot numbers not normally associated with mobile decoders, this bit
274     * may have other meanings.
275     *
276     * @return true if slot is set for forward movement, else false
277     */
278    public boolean isForward() {
279        return 0 == (dirf & LnConstants.DIRF_DIR);
280    }
281
282    private boolean[] getFuncArray() {
283        return new boolean[]{isF0(),isF1(),isF2(),isF3(),isF4(),isF5(),isF6(),isF7(),isF8(),
284            isF9(),isF10(),isF11(),isF12(),isF13(),isF14(),isF15(),isF16(),isF17(),isF18(),
285            isF19(),isF20(),isF21(),isF22(),isF23(),isF24(),isF25(),isF26(),isF27(),isF28()};
286    }
287
288    /**
289     * Return a slot Function state.
290     * <p>
291     * See individual Functions for meanings.
292     *
293     * @param Fn Function number, 0-28
294     * @return true if Function is "on", else false
295     */
296    public boolean isFunction(int Fn){
297        return getFuncArray()[Fn];
298    }
299
300    /**
301     * Returns the slot's F0 state
302     * <p>
303     * For slot numbers not normally associated with mobile decoders, this bit
304     * may have other meanings.
305     *
306     * @return true if F0 is "on", else false
307     */
308    public boolean isF0() {
309        // TODO: Consider throwing an exception (here and in similar methods)
310        // if the slot is one of the "special" slots where the slot is not
311        // storing mobile decoder funciton state in the associated bit.
312        return 0 != (dirf & LnConstants.DIRF_F0);
313    }
314
315    /**
316     * Returns the slot's F1 state
317     * <p>
318     * For slot numbers not normally associated with mobile decoders, this bit
319     * may have other meanings.
320     *
321     * @return true if F1 is "on", else false
322     */
323    public boolean isF1() {
324        return 0 != (dirf & LnConstants.DIRF_F1);
325    }
326
327    /**
328     * Returns the slot's F2 state
329     * <p>
330     * For slot numbers not normally associated with mobile decoders, this bit
331     * may have other meanings.
332     *
333     * @return true if F2 is "on", else false
334     */
335    public boolean isF2() {
336        return 0 != (dirf & LnConstants.DIRF_F2);
337    }
338
339    /**
340     * Returns the slot's F3 state
341     * <p>
342     * For slot numbers not normally associated with mobile decoders, this bit
343     * may have other meanings.
344     *
345     * @return true if F3 is "on", else false
346     */
347    public boolean isF3() {
348        return 0 != (dirf & LnConstants.DIRF_F3);
349    }
350
351    /**
352     * Returns the slot's F4 state
353     * <p>
354     * For slot numbers not normally associated with mobile decoders, this bit
355     * may have other meanings.
356     *
357     * @return true if F4 is "on", else false
358     */
359    public boolean isF4() {
360        return 0 != (dirf & LnConstants.DIRF_F4);
361    }
362
363    /**
364     * Returns the slot's F5 state
365     * <p>
366     * For slot numbers not normally associated with mobile decoders, this bit
367     * may have other meanings.
368     *
369     * @return true if F5 is "on", else false
370     */
371    public boolean isF5() {
372        return 0 != (snd & LnConstants.SND_F5);
373    }
374
375    /**
376     * Returns the slot's F6 state
377     * <p>
378     * For slot numbers not normally associated with mobile decoders, this bit
379     * may have other meanings.
380     *
381     * @return true if F6 is "on", else false
382     */
383    public boolean isF6() {
384        return 0 != (snd & LnConstants.SND_F6);
385    }
386
387    /**
388     * Returns the slot's F7 state
389     * <p>
390     * For slot numbers not normally associated with mobile decoders, this bit
391     * may have other meanings.
392     *
393     * @return true if F7 is "on", else false
394     */
395    public boolean isF7() {
396        return 0 != (snd & LnConstants.SND_F7);
397    }
398
399    /**
400     * Returns the slot's F8 state
401     * <p>
402     * For slot numbers not normally associated with mobile decoders, this bit
403     * may have other meanings.
404     *
405     * @return true if F8 is "on", else false
406     */
407    public boolean isF8() {
408        return 0 != (snd & LnConstants.SND_F8);
409    }
410
411    /**
412     * Returns the slot's F9 state
413     * <p>
414     * Some command stations do not actively remember the state of this function.
415     * JMRI attempts to track the messages which control this function, but may not
416     * reliably do so in some cases.
417     * <p>
418     * For slot numbers not normally associated with mobile decoders, this bit
419     * may have other meanings.
420     *
421     * @return true if F9 is "on", else false
422     */
423    public boolean isF9() {
424        return localF9;
425    }
426
427    /**
428     * Returns the slot's F10 state
429     * <p>
430     * Some command stations do not actively remember the state of this function.
431     * JMRI attempts to track the messages which control this function, but may not
432     * reliably do so in some cases.
433     * <p>
434     * For slot numbers not normally associated with mobile decoders, this bit
435     * may have other meanings.
436     *
437     * @return true if F10 is "on", else false
438     */
439    public boolean isF10() {
440        return localF10;
441    }
442
443    /**
444     * Returns the slot's F11 state
445     * <p>
446     * Some command stations do not actively remember the state of this function.
447     * JMRI attempts to track the messages which control this function, but may not
448     * reliably do so in some cases.
449     * <p>
450     * For slot numbers not normally associated with mobile decoders, this bit
451     * may have other meanings.
452     *
453     * @return true if F11 is "on", else false
454     */
455    public boolean isF11() {
456        return localF11;
457    }
458
459    /**
460     * Returns the slot's F12 state
461     * <p>
462     * Some command stations do not actively remember the state of this function.
463     * JMRI attempts to track the messages which control this function, but may not
464     * reliably do so in some cases.
465     * <p>
466     * For slot numbers not normally associated with mobile decoders, this bit
467     * may have other meanings.
468     *
469     * @return true if F12 is "on", else false
470     */
471    public boolean isF12() {
472        return localF12;
473    }
474
475    /**
476     * Returns the slot's F13 state
477     * <p>
478     * Some command stations do not actively remember the state of this function.
479     * JMRI attempts to track the messages which control this function, but may not
480     * reliably do so in some cases.
481     * <p>
482     * For slot numbers not normally associated with mobile decoders, this bit
483     * may have other meanings.
484     *
485     * @return true if F13 is "on", else false
486     */
487    public boolean isF13() {
488        return localF13;
489    }
490
491    /**
492     * Returns the slot's F14 state
493     * <p>
494     * Some command stations do not actively remember the state of this function.
495     * JMRI attempts to track the messages which control this function, but may not
496     * reliably do so in some cases.
497     * <p>
498     * For slot numbers not normally associated with mobile decoders, this bit
499     * may have other meanings.
500     *
501     * @return true if F14 is "on", else false
502     */
503    public boolean isF14() {
504        return localF14;
505    }
506
507    /**
508     * Returns the slot's F15 state
509     * <p>
510     * Some command stations do not actively remember the state of this function.
511     * JMRI attempts to track the messages which control this function, but may not
512     * reliably do so in some cases.
513     * <p>
514     * For slot numbers not normally associated with mobile decoders, this bit
515     * may have other meanings.
516     *
517     * @return true if F15 is "on", else false
518     */
519    public boolean isF15() {
520        return localF15;
521    }
522
523    /**
524     * Returns the slot's F16 state
525     * <p>
526     * Some command stations do not actively remember the state of this function.
527     * JMRI attempts to track the messages which control this function, but may not
528     * reliably do so in some cases.
529     * <p>
530     * For slot numbers not normally associated with mobile decoders, this bit
531     * may have other meanings.
532     *
533     * @return true if F16 is "on", else false
534     */
535    public boolean isF16() {
536        return localF16;
537    }
538
539    /**
540     * Returns the slot's F17 state
541     * <p>
542     * Some command stations do not actively remember the state of this function.
543     * JMRI attempts to track the messages which control this function, but may not
544     * reliably do so in some cases.
545     * <p>
546     * For slot numbers not normally associated with mobile decoders, this bit
547     * may have other meanings.
548     *
549     * @return true if F17 is "on", else false
550     */
551    public boolean isF17() {
552        return localF17;
553    }
554
555    /**
556     * Returns the slot's F1 state
557     * <p>
558     * Some command stations do not actively remember the state of this function.
559     * JMRI attempts to track the messages which control this function, but may not
560     * reliably do so in some cases.
561     * <p>
562     * For slot numbers not normally associated with mobile decoders, this bit
563     * may have other meanings.
564     *
565     * @return true if F1 is "on", else false
566     */
567    public boolean isF18() {
568        return localF18;
569    }
570
571    /**
572     * Returns the slot's F19 state
573     * <p>
574     * Some command stations do not actively remember the state of this function.
575     * JMRI attempts to track the messages which control this function, but may not
576     * reliably do so in some cases.
577     * <p>
578     * For slot numbers not normally associated with mobile decoders, this bit
579     * may have other meanings.
580     *
581     * @return true if F19 is "on", else false
582     */
583    public boolean isF19() {
584        return localF19;
585    }
586
587    /**
588     * Returns the slot's F20 state
589     * <p>
590     * Some command stations do not actively remember the state of this function.
591     * JMRI attempts to track the messages which control this function, but may not
592     * reliably do so in some cases.
593     * <p>
594     * For slot numbers not normally associated with mobile decoders, this bit
595     * may have other meanings.
596     *
597     * @return true if F20 is "on", else false
598     */
599    public boolean isF20() {
600        return localF20;
601    }
602
603    /**
604     * Returns the slot's F21 state
605     * <p>
606     * Some command stations do not actively remember the state of this function.
607     * JMRI attempts to track the messages which control this function, but may not
608     * reliably do so in some cases.
609     * <p>
610     * For slot numbers not normally associated with mobile decoders, this bit
611     * may have other meanings.
612     *
613     * @return true if F21 is "on", else false
614     */
615    public boolean isF21() {
616        return localF21;
617    }
618
619    /**
620     * Returns the slot's F22 state
621     * <p>
622     * Some command stations do not actively remember the state of this function.
623     * JMRI attempts to track the messages which control this function, but may not
624     * reliably do so in some cases.
625     * <p>
626     * For slot numbers not normally associated with mobile decoders, this bit
627     * may have other meanings.
628     *
629     * @return true if F22 is "on", else false
630     */
631    public boolean isF22() {
632        return localF22;
633    }
634
635    /**
636     * Returns the slot's F23 state
637     * <p>
638     * Some command stations do not actively remember the state of this function.
639     * JMRI attempts to track the messages which control this function, but may not
640     * reliably do so in some cases.
641     * <p>
642     * For slot numbers not normally associated with mobile decoders, this bit
643     * may have other meanings.
644     *
645     * @return true if F23 is "on", else false
646     */
647    public boolean isF23() {
648        return localF23;
649    }
650
651    /**
652     * Returns the slot's F24 state
653     * <p>
654     * Some command stations do not actively remember the state of this function.
655     * JMRI attempts to track the messages which control this function, but may not
656     * reliably do so in some cases.
657     * <p>
658     * For slot numbers not normally associated with mobile decoders, this bit
659     * may have other meanings.
660     *
661     * @return true if F24 is "on", else false
662     */
663    public boolean isF24() {
664        return localF24;
665    }
666
667    /**
668     * Returns the slot's F25 state
669     * <p>
670     * Some command stations do not actively remember the state of this function.
671     * JMRI attempts to track the messages which control this function, but may not
672     * reliably do so in some cases.
673     * <p>
674     * For slot numbers not normally associated with mobile decoders, this bit
675     * may have other meanings.
676     *
677     * @return true if F25 is "on", else false
678     */
679    public boolean isF25() {
680        return localF25;
681    }
682
683    /**
684     * Returns the slot's F26 state
685     * <p>
686     * Some command stations do not actively remember the state of this function.
687     * JMRI attempts to track the messages which control this function, but may not
688     * reliably do so in some cases.
689     * <p>
690     * For slot numbers not normally associated with mobile decoders, this bit
691     * may have other meanings.
692     *
693     * @return true if F26 is "on", else false
694     */
695    public boolean isF26() {
696        return localF26;
697    }
698
699    /**
700     * Returns the slot's F27 state
701     * <p>
702     * Some command stations do not actively remember the state of this function.
703     * JMRI attempts to track the messages which control this function, but may not
704     * reliably do so in some cases.
705     * <p>
706     * For slot numbers not normally associated with mobile decoders, this bit
707     * may have other meanings.
708     *
709     * @return true if F27 is "on", else false
710     */
711    public boolean isF27() {
712        return localF27;
713    }
714
715    /**
716     * Returns the slot's F28 state
717     * <p>
718     * Some command stations do not actively remember the state of this function.
719     * JMRI attempts to track the messages which control this function, but may not
720     * reliably do so in some cases.
721     * <p>
722     * For slot numbers not normally associated with mobile decoders, this bit
723     * may have other meanings.
724     *
725     * @return true if F28 is "on", else false
726     */
727    public boolean isF28() {
728        return localF28;
729    }
730
731    // loco address, speed
732    /**
733     * Returns the mobile decoder address associated with the slot.
734     * <p>
735     * Note that the returned address can encode a "short" address, a "long"
736     * address or an "alias".
737     * <p>
738     * For slot numbers not normally associated with mobile decoders, these bits
739     * may have other meanings.
740     *
741     * @return the mobile decoder address
742     */
743    public int locoAddr() {
744        return addr;
745    }
746
747    /**
748     * Returns the mobile decoder speed associated with the slot
749     * <p>
750     * If this slot object is consisted to another slot and is not the "top" of
751     * the consist, then the return value is the slot number to which this slot
752     * is consisted.
753     * <p>
754     * For slot numbers not normally associated with mobile decoders, these bits
755     * may have other meanings.
756     *
757     * @return the current speed step associated with the slot.
758     */
759    public int speed() {
760        return spd;
761    }
762
763    /**
764     * Returns the mobile decoder direction and F0-F4 bits, as used in the DIRF bits
765     * of various LocoNet messages.
766     * <p>
767     * If this slot object is consisted to another slot and is not the "top" of
768     * the consist, then the "direction" bit reflects the relative direction of this
769     * loco with respect to the loco it is consisted to, where "Reverse" means it
770     * travels in the "reverse" direction with respect to the loco to which it is
771     * consisted.
772     * <p>
773     * For slot numbers not normally associated with mobile decoders, these bits
774     * may have other meanings.
775     *
776     * @return the &lt;DIRF&gt; byte value
777     */
778    public int dirf() {
779        return dirf;
780    }
781
782    /**
783     * Returns the mobile decoder F5-F8 bits, as used in the SND bits
784     * of various LocoNet messages.
785     * <p>
786     * For slot numbers not normally associated with mobile decoders, these bits
787     * may have other meanings.
788     *
789     * @return the &lt;SND&gt; byte value
790     */
791    public int snd() {
792        return snd;
793    }
794
795    /**
796     * Returns the "Throttle ID" associated with the slot.
797     * <p>
798     * The returned value is a 14-bit integer comprised of ID1 as the least-significant
799     * bits and ID2 as the most-significant bits.
800     * <p>
801     * For slot numbers not normally associated with mobile decoders, these bits
802     * may have other meanings.
803     *
804     * @return an integer representing the throttle ID number
805     */
806    public int id() {
807        return id;
808    }
809
810    // programmer track special case accessors
811    /**
812     * Returns the programmer command associated with the slot.
813     * <p>
814     * The returned value is taken from the &lt;PCMD&gt; byte of programmer slot read
815     * and write LocoNet messages.
816     * <p>
817     * For slot numbers other than the programmer slot, these bits
818     * may have other meanings.
819     *
820     * @return the &lt;PCMD&gt; byte
821     */
822    public int pcmd() {
823        return _pcmd;
824    }
825
826    public int cvval() {
827        return snd + (ss2 & 2) * 64;
828    }
829
830    boolean localF9 = false;
831    boolean localF10 = false;
832    boolean localF11 = false;
833    boolean localF12 = false;
834    boolean localF13 = false;
835    boolean localF14 = false;
836    boolean localF15 = false;
837    boolean localF16 = false;
838    boolean localF17 = false;
839    boolean localF18 = false;
840    boolean localF19 = false;
841    boolean localF20 = false;
842    boolean localF21 = false;
843    boolean localF22 = false;
844    boolean localF23 = false;
845    boolean localF24 = false;
846    boolean localF25 = false;
847    boolean localF26 = false;
848    boolean localF27 = false;
849    boolean localF28 = false;
850
851    // methods to interact with LocoNet
852
853    /**
854     * Update the slot object to reflect the specific contents of a
855     * LocoNet message.
856     * <p>Note that the object's "slot" field
857     * is not updated by this method.
858     *
859     * @param l  a LocoNet message
860     * @throws LocoNetException if the message is not one which
861     *      contains slot-related data
862     */
863    @SuppressWarnings("fallthrough")
864    @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH")
865    public void setSlot(LocoNetMessage l) throws LocoNetException { // exception if message can't be parsed
866        // sort out valid messages, handle
867        if (slotType != SlotType.LOCO && slotType != SlotType.SYSTEM) {
868            slotType =  SlotType.LOCO;
869            log.warn("Slot [{}] not in map but reports loco, check command station type",slot);
870        }
871        switch (l.getOpCode()) {
872            case LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR:  //speed and functions
873                if (l.getElement(3) != expandedThrottleControllingID) {
874                    // Message is not from owning throttle
875                    log.debug("OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR for slot[{}] sent from throttle[{}], slot owned by [{}]",slot,l.getElement(3),expandedThrottleControllingID);
876                    return;
877                }
878                if (((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_SPEED) == 0)
879                        && (((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_MID) &&
880                        ((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_SUB))) {
881                    // speed and direction
882                    spd = l.getElement(4);
883                    dirf = dirf & 0b11011111;
884                    if ((l.getElement(1) & 0b00001000) != 0) {
885                        dirf = dirf | 0b00100000;
886                    }
887                    lastUpdateTime = System.currentTimeMillis();
888                } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F0F6) {
889                    // function grp 1
890                    dirf = dirf & 0b11100000;
891                    dirf = dirf | (l.getElement(4) & 0b00011111);
892                    snd = snd & 0b11111100;
893                    snd = snd | ((l.getElement(4) & 0b01100000) >> 5);
894                    lastUpdateTime = System.currentTimeMillis();
895                } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F7F13) {
896                    // function grp 2
897                    snd = snd & 0b11110011;
898                    snd = snd | ((l.getElement(4) & 0b00000011) << 2);
899                    localF9 = ((l.getElement(4) & 0b00000100) != 0);
900                    localF10 = ((l.getElement(4) & 0b00001000) != 0);
901                    localF11 = ((l.getElement(4) & 0b00010000) != 0);
902                    localF12 = ((l.getElement(4) & 0b00100000) != 0);
903                    localF13 = ((l.getElement(4) & 0b01000000) != 0);
904                    lastUpdateTime = System.currentTimeMillis();
905                } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F14F20) {
906                    localF14 = ((l.getElement(4) & 0b00000001) != 0);
907                    localF15 = ((l.getElement(4) & 0b00000010) != 0);
908                    localF16 = ((l.getElement(4) & 0b00000100) != 0);
909                    localF17 = ((l.getElement(4) & 0b00001000) != 0);
910                    localF18 = ((l.getElement(4) & 0b00010000) != 0);
911                    localF19 = ((l.getElement(4) & 0b00100000) != 0);
912                    localF20 = ((l.getElement(4) & 0b01000000) != 0);
913                    lastUpdateTime = System.currentTimeMillis();
914                } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28OFF
915                        || (l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28ON) {
916                    localF21 = ((l.getElement(4) & 0b00000001) != 0);
917                    localF22 = ((l.getElement(4) & 0b00000010) != 0);
918                    localF23 = ((l.getElement(4) & 0b00000100) != 0);
919                    localF24 = ((l.getElement(4) & 0b00001000) != 0);
920                    localF25 = ((l.getElement(4) & 0b00010000) != 0);
921                    localF26 = ((l.getElement(4) & 0b00100000) != 0);
922                    localF27 = ((l.getElement(4) & 0b01000000) != 0);
923                    localF28 = ((l.getElement(1) & 0b00010000) != 0);
924                    lastUpdateTime = System.currentTimeMillis();
925                }
926                notifySlotListeners();
927                break;
928            case LnConstants.OPC_EXP_RD_SL_DATA:
929            case LnConstants.OPC_EXP_WR_SL_DATA:
930                lastUpdateTime = System.currentTimeMillis();
931                stat = l.getElement(4);
932                addr = l.getElement(5) + 128 * l.getElement(6);
933                spd = l.getElement(8);
934                if (loconetProtocol == LnConstants.LOCONETPROTOCOL_UNKNOWN) {
935                    // it has to be 2
936                    loconetProtocol = LnConstants.LOCONETPROTOCOL_TWO;
937                }
938                dirf = l.getElement(10) & 0b00111111;
939                id = l.getElement(18) + 128 * l.getElement(19);
940                expandedThrottleControllingID = l.getElement(18);
941                snd = snd & 0b11111100;
942                snd = snd |  ( (l.getElement(11) & 0b01100000) >> 5) ;
943                snd = l.getElement(11) & 0b00001111;
944                trk = l.getElement(7);
945                localF9  = ((l.getElement(11) & 0b00010000 ) != 0);
946                localF10 = ((l.getElement(11) & 0b00100000 ) != 0);
947                localF11 = ((l.getElement(11) & 0b01000000 ) != 0);
948                localF12 = ((l.getElement(9)  & 0b00010000 ) != 0);
949                localF13 = ((l.getElement(12) & 0b00000001 ) != 0);
950                localF14 = ((l.getElement(12) & 0b00000010 ) != 0);
951                localF15 = ((l.getElement(12) & 0b00000100 ) != 0);
952                localF16 = ((l.getElement(12) & 0b00001000 ) != 0);
953                localF17 = ((l.getElement(12) & 0b00010000 ) != 0);
954                localF18 = ((l.getElement(12) & 0b00100000 ) != 0);
955                localF19 = ((l.getElement(12) & 0b01000000 ) != 0);
956                localF20 = ((l.getElement(9)  & 0b00100000 ) != 0);
957                localF21 = ((l.getElement(13) & 0b00000001 ) != 0);
958                localF22 = ((l.getElement(13) & 0b00000010 ) != 0);
959                localF23 = ((l.getElement(13) & 0b00000100 ) != 0);
960                localF24 = ((l.getElement(13) & 0b00001000 ) != 0);
961                localF25 = ((l.getElement(13) & 0b00010000 ) != 0);
962                localF26 = ((l.getElement(13) & 0b00100000 ) != 0);
963                localF27 = ((l.getElement(13) & 0b01000000 ) != 0);
964                localF28 = ((l.getElement(9)  & 0b01000000 ) != 0);
965                leadSlot = (((l.getElement(9) & 0x03)   * 128) + l.getElement(8) );
966                notifySlotListeners();
967                break;
968            case LnConstants.OPC_SL_RD_DATA:
969                lastUpdateTime = System.currentTimeMillis();
970            //fall through
971            case LnConstants.OPC_WR_SL_DATA: {
972                if (l.getElement(1) != 0x0E) {
973                    return;  // not an appropriate reply
974                }            // valid, so fill contents
975                if (slot != l.getElement(2)) {
976                    log.error("Asked to handle message not for this slot ({}) {}", slot, l);
977                }
978                if (loconetProtocol == LnConstants.LOCONETPROTOCOL_UNKNOWN) {
979                    loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE;   // all it can be...
980                }
981                stat = l.getElement(3);
982                _pcmd = l.getElement(4);
983                addr = l.getElement(4) + 128 * l.getElement(9);
984                spd = l.getElement(5);
985                dirf = l.getElement(6);
986                trk = l.getElement(7);
987                ss2 = l.getElement(8);
988                // item 9 is in add2
989                snd = l.getElement(10);
990                id = l.getElement(11) + 128 * l.getElement(12);
991                expandedThrottleControllingID = l.getElement(11);
992
993                notifySlotListeners();
994                return;
995            }
996            case LnConstants.OPC_SLOT_STAT1:
997                if (slot != l.getElement(1)) {
998                    log.error("Asked to handle message not for this slot {}", l);
999                }
1000                stat = l.getElement(2);
1001                notifySlotListeners();
1002                lastUpdateTime = System.currentTimeMillis();
1003                return;
1004            case LnConstants.OPC_LOCO_SND: {
1005                // set sound functions in slot - first, clear bits
1006                snd &= ~(LnConstants.SND_F5 | LnConstants.SND_F6
1007                        | LnConstants.SND_F7 | LnConstants.SND_F8);
1008                // and set them as masked
1009                snd |= ((LnConstants.SND_F5 | LnConstants.SND_F6
1010                        | LnConstants.SND_F7 | LnConstants.SND_F8) & l.getElement(2));
1011                notifySlotListeners();
1012                lastUpdateTime = System.currentTimeMillis();
1013                return;
1014            }
1015            case LnConstants.OPC_LOCO_DIRF: {
1016                // When slot is consist-mid or consist-sub, this LocoNet Opcode
1017                // can only change the functions; direction cannot be changed.
1018                if (((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_MID) ||
1019                        ((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_SUB)) {
1020                    // set functions in slot - first, clear bits, preserving DIRF_DIR bit
1021                    dirf &= LnConstants.DIRF_DIR | (~(LnConstants.DIRF_F0
1022                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1023                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4));
1024                    // and set the function bits from the LocoNet message
1025                    dirf += ((LnConstants.DIRF_F0
1026                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1027                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2));
1028                } else {
1029                    // set direction, functions in slot - first, clear bits
1030                    dirf &= ~(LnConstants.DIRF_DIR | LnConstants.DIRF_F0
1031                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1032                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4);
1033                    // and set them as masked
1034                    dirf += ((LnConstants.DIRF_DIR | LnConstants.DIRF_F0
1035                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1036                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2));
1037
1038                }
1039                notifySlotListeners();
1040                lastUpdateTime = System.currentTimeMillis();
1041                return;
1042            }
1043            case LnConstants.OPC_MOVE_SLOTS:
1044            case LnConstants.OPC_LINK_SLOTS:
1045            case LnConstants.OPC_UNLINK_SLOTS: {
1046                // change in slot status, if any, will be reported by the reply,
1047                // so don't need to do anything here (but could)
1048                lastUpdateTime = System.currentTimeMillis();
1049                notifySlotListeners();
1050                return;
1051            }
1052            case LnConstants.OPC_LOCO_SPD: {
1053                // This opcode has no effect on the slot's speed setting if the
1054                // slot is mid-consist or sub-consist.
1055                if (((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_MID) &&
1056                        ((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_SUB)) {
1057
1058                    spd = l.getElement(2);
1059                    notifySlotListeners();
1060                    lastUpdateTime = System.currentTimeMillis();
1061                } else {
1062                    log.info("Ignoring speed change for slot {} marked as consist-mid or consist-sub.", slot);
1063                }
1064                return;
1065            }
1066            case LnConstants.OPC_CONSIST_FUNC: {
1067                // This opcode can be sent to a slot which is marked as mid-consist
1068                // or sub-consist.  Do not pay attention to this message if the
1069                // slot is not mid-consist or sub-consist.
1070                if (((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_MID) ||
1071                        ((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_SUB)) {
1072                    // set functions in slot - first, clear bits, preserving DIRF_DIR bit
1073                    dirf &= LnConstants.DIRF_DIR | (~(LnConstants.DIRF_F0
1074                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1075                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4));
1076                    // and set the function bits from the LocoNet message
1077                    dirf += ((LnConstants.DIRF_F0
1078                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1079                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2));
1080                    notifySlotListeners();
1081                    lastUpdateTime = System.currentTimeMillis();
1082                }
1083                return;
1084            }
1085            case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL: {
1086                if (l.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN) {
1087                    // IB function message
1088                    int data = l.getElement(4);
1089                    switch (l.getElement(3)) {
1090                        case LnConstants.RE_IB1_SPECIAL_F5_F11_TOKEN:
1091                            // under 8 are kept in the slot, not local variables
1092                            localF9 = ((data & LnConstants.RE_IB1_F9_MASK) != 0);
1093                            localF10 = ((data & LnConstants.RE_IB1_F10_MASK) != 0);
1094                            localF11 = ((data & LnConstants.RE_IB1_F11_MASK) != 0);
1095                            return;
1096                        case LnConstants.RE_IB2_SPECIAL_F13_F19_TOKEN:
1097                            localF13 = ((data & LnConstants.RE_IB2_F13_MASK) != 0);
1098                            localF14 = ((data & LnConstants.RE_IB2_F14_MASK) != 0);
1099                            localF15 = ((data & LnConstants.RE_IB2_F15_MASK) != 0);
1100                            localF16 = ((data & LnConstants.RE_IB2_F16_MASK) != 0);
1101                            localF17 = ((data & LnConstants.RE_IB2_F17_MASK) != 0);
1102                            localF18 = ((data & LnConstants.RE_IB2_F18_MASK) != 0);
1103                            localF19 = ((data & LnConstants.RE_IB2_F19_MASK) != 0);
1104                            return;
1105                        case LnConstants.RE_IB2_SPECIAL_F21_F27_TOKEN:
1106                            localF21 = ((data & LnConstants.RE_IB2_F21_MASK) != 0);
1107                            localF22 = ((data & LnConstants.RE_IB2_F22_MASK) != 0);
1108                            localF23 = ((data & LnConstants.RE_IB2_F23_MASK) != 0);
1109                            localF24 = ((data & LnConstants.RE_IB2_F24_MASK) != 0);
1110                            localF25 = ((data & LnConstants.RE_IB2_F25_MASK) != 0);
1111                            localF26 = ((data & LnConstants.RE_IB2_F26_MASK) != 0);
1112                            localF27 = ((data & LnConstants.RE_IB2_F27_MASK) != 0);
1113                            return;
1114                        case LnConstants.RE_IB2_SPECIAL_F20_F28_TOKEN:
1115                            localF12 = ((data & LnConstants.RE_IB2_SPECIAL_F12_MASK) != 0);
1116                            localF20 = ((data & LnConstants.RE_IB2_SPECIAL_F20_MASK) != 0);
1117                            localF28 = ((data & LnConstants.RE_IB2_SPECIAL_F28_MASK) != 0);
1118                            return;
1119                        default:
1120                            log.debug("Found IB RE_OPC_IB2_SPECIAL message of {}", l);
1121                            return;
1122                    }
1123                }
1124                int src = slot;
1125                int dest = ((l.getElement(3) & 0x07) * 128) + (l.getElement(4) & 0x7f);
1126                // null move or change status or consisting or?
1127                if ((l.getElement(1) & 0b11111000) == 0b00111000) {
1128                    if (((l.getElement(3) & 0b01110000) == 0b01100000)) {
1129                        stat = l.getElement(4);
1130                        notifySlotListeners();
1131                        return;
1132                    } else if ((l.getElement(3) & 0b01110000) == 0b01010000) {
1133                        // unconsisting returns slot contents so do nothing to this slot
1134                        return;
1135                    } else if ((l.getElement(3) & 0b01110000) == 0b01000000) {
1136                        //consisting do something?
1137                        //Set From slot as slave to slot as master
1138                        stat = stat | LnConstants.CONSIST_TOP;
1139                        notifySlotListeners();
1140                        return;
1141                    } else if (src == 0 && dest == 0) {
1142                        stat = stat & ~LnConstants.LOCO_IN_USE;
1143                        log.debug("set idle");
1144                        notifySlotListeners();
1145                        return;
1146                    }
1147                }
1148                return;
1149            }
1150            default: {
1151                throw new LocoNetException("message can't be parsed"); // NOI18N
1152            }
1153        }
1154    }
1155
1156    /**
1157     * Sets F9 through F28 (as appropriate) from data extracted from LocoNet
1158     * "OPC_IMM_PACKET" message.
1159     * <p>If the pkt parameter does not contain data from an appropriate
1160     * OPC_IMM_PACKET message, the pkt is ignored and the slot object remains
1161     * unchanged.
1162     *
1163     * @param pkt is a "long" consisting of four bytes extracted from a LocoNet
1164     * "OPC_IMM_PACKET" message.
1165     * <p>
1166     * {@link jmri.jmrix.loconet.SlotManager#getDirectDccPacket(LocoNetMessage m)}
1167     */
1168    public void functionMessage(long pkt) {
1169        // parse for which set of functions
1170        if ((pkt & 0xFFFFFF0) == 0xA0) {
1171            // F9-12
1172            localF9 = ((pkt & 0x01) != 0);
1173            localF10 = ((pkt & 0x02) != 0);
1174            localF11 = ((pkt & 0x04) != 0);
1175            localF12 = ((pkt & 0x08) != 0);
1176            notifySlotListeners();
1177        } else if ((pkt & 0xFFFFFF00) == 0xDE00) {
1178            // check F13-20
1179            localF13 = ((pkt & 0x01) != 0);
1180            localF14 = ((pkt & 0x02) != 0);
1181            localF15 = ((pkt & 0x04) != 0);
1182            localF16 = ((pkt & 0x08) != 0);
1183            localF17 = ((pkt & 0x10) != 0);
1184            localF18 = ((pkt & 0x20) != 0);
1185            localF19 = ((pkt & 0x40) != 0);
1186            localF20 = ((pkt & 0x80) != 0);
1187            notifySlotListeners();
1188        } else if ((pkt & 0xFFFFFF00) == 0xDF00) {
1189            // check F21-28
1190            localF21 = ((pkt & 0x01) != 0);
1191            localF22 = ((pkt & 0x02) != 0);
1192            localF23 = ((pkt & 0x04) != 0);
1193            localF24 = ((pkt & 0x08) != 0);
1194            localF25 = ((pkt & 0x10) != 0);
1195            localF26 = ((pkt & 0x20) != 0);
1196            localF27 = ((pkt & 0x40) != 0);
1197            localF28 = ((pkt & 0x80) != 0);
1198            notifySlotListeners();
1199        }
1200    }
1201
1202    /**
1203     * Update the decoder type bits in STAT1 (D2, D1, D0)
1204     *
1205     * @param status New values for STAT1 (D2, D1, D0)
1206     * @return Formatted LocoNet message to change value.
1207     */
1208    public LocoNetMessage writeMode(int status) {
1209        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) {
1210            LocoNetMessage l = new LocoNetMessage(4);
1211            l.setOpCode(LnConstants.OPC_SLOT_STAT1);
1212            l.setElement(1, slot);
1213            l.setElement(2, (stat & ~LnConstants.DEC_MODE_MASK) | status);
1214            return l;
1215        } else {
1216            LocoNetMessage l = new LocoNetMessage(6);
1217            l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL);
1218            l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ;
1219            l.setElement(2, slot & 0x7f);
1220            l.setElement(3, 0x60);
1221            l.setElement(4, (stat & ~LnConstants.DEC_MODE_MASK) | status);
1222            return l;
1223        }
1224    }
1225
1226    /**
1227     * Sets the object's ID value and returns a LocoNet message to inform the
1228     * command station that the throttle ID has been changed.
1229     * @param newID  the new ID number to set into the slot object
1230     * @return a LocoNet message containing a "Slot Write" message to inform the
1231     * command station that a specific throttle is controlling the slot.
1232     */
1233    public LocoNetMessage writeThrottleID(int newID) {
1234        id = (newID & 0x17F);
1235        return writeSlot();
1236    }
1237
1238    /**
1239     * Set the throttle ID in the slot
1240     *
1241     * @param throttleId full id
1242     */
1243    public void setThrottleIdentity(int throttleId) {
1244        id = throttleId;
1245    }
1246
1247    /**
1248     * Get the throttle ID in the slot
1249     *
1250     *@return the Id of the Throttle
1251     */
1252    public int getThrottleIdentity() {
1253        return id;
1254    }
1255
1256    public int getLeadSlot() {
1257        return leadSlot;
1258    }
1259
1260    /**
1261     * Update the status mode bits in STAT1 (D5, D4)
1262     *
1263     * @param status New values for STAT1 (D5, D4)
1264     * @return Formatted LocoNet message to change value.
1265     */
1266    public LocoNetMessage writeStatus(int status) {
1267        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) {
1268            LocoNetMessage l = new LocoNetMessage(4);
1269            l.setOpCode(LnConstants.OPC_SLOT_STAT1);
1270            l.setElement(1, slot);
1271            l.setElement(2, (stat & ~LnConstants.LOCOSTAT_MASK) | status);
1272            return l;
1273        } else {
1274            LocoNetMessage l = new LocoNetMessage(6);
1275            l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL);
1276            l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ;
1277            l.setElement(2, slot & 0x7f);
1278            l.setElement(3, 0x60);
1279            l.setElement(4, (stat & ~LnConstants.LOCOSTAT_MASK) | status);
1280            return l;
1281        }
1282    }
1283
1284    /**
1285     * Update Speed
1286     *
1287     * @param speed new speed
1288     * @return Formatted LocoNet message to change value.
1289     */
1290    public LocoNetMessage writeSpeed(int speed) {
1291        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) {
1292            LocoNetMessage l = new LocoNetMessage(4);
1293            l.setOpCode(LnConstants.OPC_LOCO_SPD);
1294            l.setElement(1, slot );
1295            l.setElement(2, speed);
1296            return l;
1297        } else {
1298            LocoNetMessage l = new LocoNetMessage(6);
1299            l.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR);
1300            l.setElement(1, ((slot / 128) & 0x03) | ((dirf &  LnConstants.DIRF_DIR ) >> 2) );
1301            l.setElement(2, slot & 0x7f);
1302            l.setElement(3, (id & 0x7f));
1303            l.setElement(4, speed);
1304            return l;
1305        }
1306    }
1307
1308    /**
1309     * Create LocoNet message which dispatches this slot
1310     *
1311     * Note that the invoking method ought to invoke the slot's NotifySlotListeners
1312     * method to inform any other interested parties that the slot status has changed.
1313     *
1314     * @return LocoNet message which "dispatches" the slot
1315    */
1316    public LocoNetMessage dispatchSlot() {
1317        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) {
1318            LocoNetMessage l = new LocoNetMessage(4);
1319            l.setOpCode(LnConstants.OPC_MOVE_SLOTS);
1320            l.setElement(1, slot);
1321            l.setElement(2, 0);
1322            return l;
1323        } else {
1324            LocoNetMessage l = new LocoNetMessage(6);
1325            l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL);
1326            l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ;
1327            l.setElement(2, slot & 0x7f);
1328            l.setElement(3, 0);
1329            l.setElement(4, 0);
1330            return l;
1331        }
1332    }
1333
1334    /**
1335     * Create a message to perform a null move on this slot.
1336     * @return correct LocoNetMessage for protocol being used.
1337     */
1338    public LocoNetMessage writeNullMove() {
1339        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) {
1340            // perform the null slot move for low numbered slots
1341            LocoNetMessage msg = new LocoNetMessage(4);
1342            msg.setOpCode(LnConstants.OPC_MOVE_SLOTS);
1343            msg.setElement(1, slot);
1344            msg.setElement(2, slot);
1345            return (msg);
1346        }
1347        // or the null move for higher numbered slots
1348        LocoNetMessage msg = new LocoNetMessage(6);
1349        msg.setOpCode(0xd4);
1350        msg.setElement(1, (slot / 128) | 0b00111000);
1351        msg.setElement(2, slot & 0b01111111);
1352        msg.setElement(3, (slot / 128) & 0b00000111);
1353        msg.setElement(4, slot & 0b01111111);
1354        return msg;
1355    }
1356
1357    /**
1358     * Create a LocoNet OPC_SLOT_STAT1 message which releases this slot to the
1359     * "Common" state
1360     *
1361     * The invoking method must send the returned LocoNet message to LocoNet in
1362     * order to have a useful effect.
1363     *
1364     * Upon receipt of the echo of the transmitted OPC_SLOT_STAT1 message, the
1365     * LocoNetSlot object will notify its listeners.
1366     *
1367     * @return LocoNet message which "releases" the slot to the "Common" state
1368    */
1369    public LocoNetMessage releaseSlot() {
1370        return writeStatus(LnConstants.LOCO_COMMON);
1371    }
1372
1373    /**
1374     * Creates a LocoNet "OPC_WR_SL_DATA" message containing the current state of
1375     * the LocoNetSlot object.
1376     *
1377     * @return a LocoNet message which can be used to inform the command station
1378     * of a change in the slot contents.
1379     */
1380    public LocoNetMessage writeSlot() {
1381        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO || slot == LnConstants.FC_SLOT) { //special case for fc
1382            LocoNetMessage l = new LocoNetMessage(14);
1383            l.setOpCode(LnConstants.OPC_WR_SL_DATA);
1384            l.setElement(1, 0x0E);
1385            l.setElement(2, slot & 0x7F);
1386            l.setElement(3, stat & 0x7F);
1387            l.setElement(4, addr & 0x7F);
1388            l.setElement(9, (addr / 128) & 0x7F);
1389            l.setElement(5, spd & 0x7F);
1390            l.setElement(6, dirf & 0x7F);
1391            l.setElement(7, trk & 0x7F);
1392            l.setElement(8, ss2 & 0x7F);
1393            // item 9 is add2
1394            l.setElement(10, snd & 0x7F);
1395            l.setElement(11, id & 0x7F);
1396            l.setElement(12, (id / 128) & 0x7F);
1397            return l;
1398        }
1399        LocoNetMessage l = new LocoNetMessage(21);
1400        l.setOpCode(LnConstants.OPC_EXP_WR_SL_DATA);
1401        l.setElement(1, 0x15);
1402        l.setElement(2, (slot / 128) & 0x03);
1403        l.setElement(3, slot & 0x7F);
1404        l.setElement(4, stat & 0x7F);
1405        l.setElement(6, (addr / 128) & 0x7F);
1406        l.setElement(5, addr & 0x7F);
1407        l.setElement(7, ( trk | 0x40 ) & 0x7F);  // track power status and Expanded slot protocol
1408        l.setElement(8, spd & 0x7F);
1409        l.setElement(9, (isF12() ? 0b00010000 : 0x00 )
1410                | (isF20() ? 0b00100000 : 0x00)
1411                | (isF28() ? 0b01000000 : 0x00));
1412        l.setElement(10, ( isForward() ? 0x00 : 0x00100000)
1413                | (isF0() ? 0b00010000 : 0x00)
1414                | (isF1() ? 0b00000001 : 0x00)
1415                | (isF2() ? 0b00000010 : 0x00)
1416                | (isF3() ? 0b00000100 : 0x00)
1417                | (isF4() ? 0b00001000 : 0x00));
1418        l.setElement(11, ( isF5() ? 0b00000001 : 0x00)
1419                | ( isF6() ? 0b00000010 : 0x00)
1420                | ( isF7() ? 0b00000100 : 0x00)
1421                | ( isF8() ? 0b00001000 : 0x00)
1422                | ( isF9() ? 0b00010000 : 0x00)
1423                | (isF10() ? 0b00100000 : 0x00)
1424                | (isF11() ? 0b01000000 : 0x00));
1425        l.setElement(12,( isF13() ? 0b00000001 : 0x00)
1426                | (isF14() ? 0b00000010 : 0x00)
1427                | (isF15() ? 0b00000100 : 0x00)
1428                | (isF16() ? 0b00001000 : 0x00)
1429                | (isF17() ? 0b00010000 : 0x00)
1430                | (isF18() ? 0b00100000 : 0x00)
1431                | (isF19() ? 0b01000000 : 0x00));
1432        l.setElement(13,( isF21() ? 0b00000001 : 0x00)
1433                | (isF22() ? 0b00000010 : 0x00)
1434                | (isF23() ? 0b00000100 : 0x00)
1435                | (isF24() ? 0b00001000 : 0x00)
1436                | (isF25() ? 0b00010000 : 0x00)
1437                | (isF26() ? 0b00100000 : 0x00)
1438                | (isF27() ? 0b01000000 : 0x00));
1439        l.setElement(18, id & 0x7F);
1440        l.setElement(19, (id / 128) & 0x7F);
1441        return l;
1442    }
1443
1444    // data values to echo slot contents
1445    final private int slot;   // <SLOT#> is the number of the slot that was read.
1446    private boolean isInitialized; // set when full initilization is complete with the throttle ID.
1447    private int loconetProtocol; // protocol used by the slot.
1448    private SlotType slotType; // system, loco, unknown
1449    private int stat; // <STAT> is the status of the slot
1450    private int addr; // full address of the loco, made from
1451    //    <ADDR> is the low 7 (0-6) bits of the Loco address
1452    //    <ADD2> is the high 7 bits (7-13) of the 14-bit loco address
1453    private int spd; // <SPD> is the current speed (0-127)
1454    private int dirf; // <DIRF> is the current Direction and the setting for functions F0-F4
1455    private int trk = 7; // <TRK> is the global track status
1456    private int ss2; // <SS2> is the an additional slot status
1457    private int snd;  // <SND> is the settings for functions F5-F8
1458    private int id;  // throttle id, made from
1459    //     <ID1> and <ID2> normally identify the throttle controlling the loco
1460    private int expandedThrottleControllingID; //the throttle ID byte that is used in sending commands that require a throttle ID. (ID1)
1461    private int leadSlot; // the top slot for this slot in a consist.
1462
1463    private int _pcmd;  // hold pcmd and pstat for programmer
1464
1465    private long lastUpdateTime; // Time of last update for detecting stale slots
1466
1467    // data members to hold contact with the slot listeners
1468    final private List<SlotListener> slotListeners = new ArrayList<>();
1469
1470    /**
1471     * Registers a slot listener if it is not already registered.
1472     *
1473     * @param l  a slot listener
1474     */
1475    public synchronized void addSlotListener(SlotListener l) {
1476        // add only if not already registered
1477        if (!slotListeners.contains(l)) {
1478            slotListeners.add(l);
1479        }
1480    }
1481
1482    /**
1483     * Un-registers a slot listener.
1484     *
1485     * @param l  a slot listener
1486     */
1487    public synchronized void removeSlotListener(SlotListener l) {
1488        if (slotListeners.contains(l)) {
1489            slotListeners.remove(l);
1490        }
1491    }
1492
1493    /**
1494     * Returns the timestamp when this LocoNetSlot was updated by some LocoNet
1495     * message.
1496     *
1497     * @return last time the slot info was updated
1498     */
1499    public long getLastUpdateTime() {
1500        return lastUpdateTime;
1501    }
1502
1503    /**
1504     * Notifies all listeners that this slot has been changed in some way.
1505     */
1506    public void notifySlotListeners() {
1507        // make a copy of the listener list to synchronized not needed for transmit
1508        List<SlotListener> v;
1509        synchronized (this) {
1510            v = new ArrayList<>(slotListeners);
1511        }
1512        log.debug("notify {} SlotListeners",v.size()); // NOI18N
1513        // forward to all listeners
1514        int cnt = v.size();
1515        for (int i = 0; i < cnt; i++) {
1516            SlotListener client = v.get(i);
1517            client.notifyChangedSlot(this);
1518        }
1519    }
1520
1521    /**
1522     * For fast-clock slot, set a "CLK_CNTRL" bit On. This method logs an error
1523     * if invoked for a slot other than the fast-clock slot.
1524     *
1525     * @param val is the new "CLK_CNTRL" bit value to turn On
1526     */
1527    public void setFcCntrlBitOn(int val) {
1528        // TODO: consider throwing a LocoNetException if issued for a slot other
1529        // than the "fast clock slot".
1530        if (getSlot() != LnConstants.FC_SLOT) {
1531            log.error("setFcCntrl invalid for slot [{}]", getSlot());
1532        }
1533        snd |= val;
1534    }
1535
1536    /**
1537     * For fast-clock slot, set a "CLK_CNTRL" bit Off. This method logs an error
1538     * if invoked for a slot other than the fast-clock slot.
1539     *
1540     * @param val is the new "CLK_CNTRL" bit value to turn Off
1541     */
1542    public void setFcCntrlBitOff(int val) {
1543        // TODO: consider throwing a LocoNetException if issued for a slot other
1544        // than the "fast clock slot".
1545        if (getSlot() != LnConstants.FC_SLOT) {
1546            log.error("setFcCntrl invalid for slot [{}]" , getSlot());
1547        }
1548        snd &= ~val;
1549    }
1550
1551    /**
1552     * Get the track status byte (location 7)
1553     * <p>
1554     * Note that the &lt;TRK&gt; byte is not accurate on some command stations.
1555     *
1556     * @return the effective &lt;TRK&gt; byte
1557     */
1558    public int getTrackStatus() { return trk; }
1559
1560    /**
1561     * Set the track status byte (location 7)
1562     * <p>
1563     * Note that setting the LocoNetSlot object's track status may result in a
1564     * change to the command station's actual track status if the slot's status
1565     * is communicated to the command station via an OPC_WR_DL_DATA LocoNet message.
1566     *
1567     * @param status is the new track status value.
1568     */
1569    public void setTrackStatus(int status) { trk = status; }
1570
1571    /**
1572     * Return the days value from the slot.  Only valid for fast-clock slot.
1573     * <p>
1574     * This method logs an error if invoked for a slot other than the fast-clock slot.
1575     *
1576     * @return "Days" value currently in fast-clock slot.
1577     */
1578    public int getFcDays() {
1579        // TODO: consider throwing a LocoNetException if issued for a slot other
1580        // than the "fast clock slot".
1581        if (getSlot() != LnConstants.FC_SLOT) {
1582            log.error("getFcDays invalid for slot {}", getSlot());
1583        }
1584        return (addr & 0x3f80) / 0x80;
1585    }
1586
1587    /**
1588     * For fast-clock slot, set "days" value.
1589     * <p>
1590     * Note that the new days value is not effective until a LocoNet
1591     * message is sent which writes the fast-clock slot data.
1592     * <p>
1593     * This method logs an error if invoked for a slot other than the fast-clock slot.
1594     *
1595     * @param val is the new fast-clock "days" value
1596     */
1597    public void setFcDays(int val) {
1598        // TODO: consider throwing a LocoNetException if issued for a slot other
1599        // than the "fast clock slot".
1600        if (getSlot() != LnConstants.FC_SLOT) {
1601            log.error("setFcDays invalid for slot {}", getSlot());
1602        }
1603        addr = val * 128 + (addr & 0x7f);
1604    }
1605
1606    /**
1607     * Return the hours value from the slot.  Only valid for fast-clock slot.
1608     * <p>
1609     * This method logs an error if invoked for a slot other than the fast-clock slot.
1610     *
1611     * @return "Hours" value currently stored in fast clock slot.
1612     */
1613    public int getFcHours() {
1614        // TODO: consider throwing a LocoNetException if issued for a slot other
1615        // than the "fast clock slot".
1616        if (getSlot() != LnConstants.FC_SLOT) {
1617            log.error("getFcHours invalid for slot {}", getSlot());
1618        }
1619        int temp = ((256 - ss2) & 0x7F) % 24;
1620        return (24 - temp) % 24;
1621    }
1622
1623    /**
1624     * For fast-clock slot, set "hours" value.
1625     * <p>
1626     * Note that the new hours value is not effective until a LocoNet
1627     * message is sent which writes the fast-clock slot data.
1628     * <p>
1629     * This method logs an error if invoked for a slot other than the fast-clock slot.
1630     *
1631     * @param val is the new fast-clock "hours" value
1632     */
1633    public void setFcHours(int val) {
1634        // TODO: consider throwing a LocoNetException if issued for a slot other
1635        // than the "fast clock slot".
1636        if (getSlot() != LnConstants.FC_SLOT) {
1637            log.error("setFcHours invalid for slot {}", getSlot());
1638        }
1639        ss2 = (256 - (24 - val)) & 0x7F;
1640    }
1641
1642    /**
1643     * Return the minutes value from the slot.  Only valid for fast-clock slot.
1644     * <p>
1645     * This method logs an error if invoked for a slot other than the fast-clock slot.
1646     *
1647     * @return Return minutes value currently stored in the fast clock slot.
1648     */
1649    public int getFcMinutes() {
1650        // TODO: consider throwing a LocoNetException if issued for a slot other
1651        // than the "fast clock slot".
1652        if (getSlot() != LnConstants.FC_SLOT) {
1653            log.error("getFcMinutes invalid for slot {}", getSlot());
1654        }
1655        int temp = ((255 - dirf) & 0x7F) % 60;
1656        return (60 - temp) % 60;
1657    }
1658
1659    /**
1660     * For fast-clock slot, set "minutes" value.
1661     * <p>
1662     * Note that the new minutes value is not effective until a LocoNet
1663     * message is sent which writes the fast-clock slot data.
1664     * <p>
1665     * This method logs an error if invoked for a slot other than the fast-clock slot.
1666     *
1667     * @param val is the new fast-clock "minutes" value
1668     */
1669    public void setFcMinutes(int val) {
1670        // TODO: consider throwing a LocoNetException if issued for a slot other
1671        // than the "fast clock slot".
1672        if (getSlot() != LnConstants.FC_SLOT) {
1673            log.error("setFcMinutes invalid for slot {}", getSlot());
1674        }
1675        dirf = (255 - (60 - val)) & 0x7F;
1676    }
1677
1678    /**
1679     * Return the fractional minutes value from the slot.  Only valid for fast-
1680     * clock slot.
1681     * <p>
1682     * This method logs an error if invoked for a slot other than the fast-clock slot.
1683     *
1684     * @return Return frac_mins field which is the number of 65ms ticks until
1685     *         then next minute rollover. These ticks step at the current fast
1686     *         clock rate
1687     */
1688    public int getFcFracMins() {
1689        // TODO: consider throwing a LocoNetException if issued for a slot other
1690        // than the "fast clock slot".
1691        if (getSlot() != LnConstants.FC_SLOT) {
1692            log.error("getFcFracMins invalid for slot {}", getSlot());
1693        }
1694        return ((addr & 0x7F) | ((spd & 0x7F) << 8));
1695    }
1696
1697    /**
1698     * Set the "frac_mins" value.
1699     * This has to be calculated as required by the Command Station,
1700     * then bit shifted if required.
1701     * It is comprised of a base number and the distance from the base to 0x8000
1702     * or 0x4000 deoending on command station.
1703     * It is read and written as is LO,HO and loses the bit 7 of the LO.
1704     * It was never intended for external use.
1705     * The base can be found by setting the clock to 0xXX7F, with a rate of 1
1706     * and pounding the clock every 250 to 100 msecs until it roles.
1707     * <p>
1708     * Note 1: The new fractional minutes value is not effective until a LocoNet slot write happens
1709     * <p>
1710     * Note 2: DT40x &amp; DT500 throttles ignore this value, and set only the whole minutes.
1711     * <p>
1712     * This method logs an error if invoked for a slot other than the fast-clock slot.
1713     * @param val is the new fast-clock "fractional minutes" including the base, and bit shifted if required.
1714     */
1715    public void setFcFracMins(int val) {
1716        // TODO: consider throwing a LocoNetException if issued for a slot other
1717        // than the "fast clock slot".
1718        if (getSlot() != LnConstants.FC_SLOT) {
1719            log.error("setFcFracMins invalid for slot {}", getSlot());
1720        }
1721        int temp = 0x7F7F & val;
1722        addr = (addr & 0x7F00) | (temp & 0x7F);
1723        spd = (temp >> 8) & 0x7F;
1724    }
1725
1726    /**
1727     * Get the fast-clock rate.  Only valid for fast-clock slot.
1728     * <p>
1729     * This method logs an error if invoked for a slot other than the fast-clock slot.
1730     *
1731     * @return Rate stored in fast clock slot.
1732     */
1733    public int getFcRate() {
1734        // TODO: consider throwing a LocoNetException if issued for a slot other
1735        // than the "fast clock slot".
1736        if (getSlot() != LnConstants.FC_SLOT) {
1737            log.error("getFcRate invalid for slot {}", getSlot());
1738        }
1739        return stat;
1740    }
1741
1742    /**
1743     * For fast-clock slot, set "rate" value.
1744     * <p>
1745     * Note that the new rate is not effective until a LocoNet message is sent
1746     * which writes the fast-clock slot data.
1747     * <p>
1748     * This method logs an error if invoked for a slot other than the fast-clock slot.
1749     *
1750     * @param val is the new fast-clock rate
1751     */
1752    public void setFcRate(int val) {
1753        // TODO: consider throwing a LocoNetException if issued for a slot other
1754        // than the "fast clock slot".
1755        if (getSlot() != LnConstants.FC_SLOT) {
1756            log.error("setFcRate invalid for slot {}", getSlot());
1757        }
1758        stat = val & 0x7F;
1759    }
1760
1761    private final static Logger log = LoggerFactory.getLogger(LocoNetSlot.class);
1762}