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
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                } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F0F6) {
888                    // function grp 1
889                    dirf = dirf & 0b11100000;
890                    dirf = dirf | (l.getElement(4) & 0b00011111);
891                    snd = snd & 0b11111100;
892                    snd = snd | ((l.getElement(4) & 0b01100000) >> 5);
893                } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F7F13) {
894                    // function grp 2
895                    snd = snd & 0b11110011;
896                    snd = snd | ((l.getElement(4) & 0b00000011) << 2);
897                    localF9 = ((l.getElement(4) & 0b00000100) != 0);
898                    localF10 = ((l.getElement(4) & 0b00001000) != 0);
899                    localF11 = ((l.getElement(4) & 0b00010000) != 0);
900                    localF12 = ((l.getElement(4) & 0b00100000) != 0);
901                    localF13 = ((l.getElement(4) & 0b01000000) != 0);
902                } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F14F20) {
903                    localF14 = ((l.getElement(4) & 0b00000001) != 0);
904                    localF15 = ((l.getElement(4) & 0b00000010) != 0);
905                    localF16 = ((l.getElement(4) & 0b00000100) != 0);
906                    localF17 = ((l.getElement(4) & 0b00001000) != 0);
907                    localF18 = ((l.getElement(4) & 0b00010000) != 0);
908                    localF19 = ((l.getElement(4) & 0b00100000) != 0);
909                    localF20 = ((l.getElement(4) & 0b01000000) != 0);
910                } else if ((l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28OFF
911                        || (l.getElement(1) & LnConstants.OPC_EXP_SEND_SUB_CODE_MASK_FUNCTION) == LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28ON) {
912                    localF21 = ((l.getElement(4) & 0b00000001) != 0);
913                    localF22 = ((l.getElement(4) & 0b00000010) != 0);
914                    localF23 = ((l.getElement(4) & 0b00000100) != 0);
915                    localF24 = ((l.getElement(4) & 0b00001000) != 0);
916                    localF25 = ((l.getElement(4) & 0b00010000) != 0);
917                    localF26 = ((l.getElement(4) & 0b00100000) != 0);
918                    localF27 = ((l.getElement(4) & 0b01000000) != 0);
919                    localF28 = ((l.getElement(1) & 0b00010000) != 0);
920                }
921                notifySlotListeners();
922                break;
923            case LnConstants.OPC_EXP_RD_SL_DATA:
924            case LnConstants.OPC_EXP_WR_SL_DATA:
925                lastUpdateTime = System.currentTimeMillis();
926                stat = l.getElement(4);
927                addr = l.getElement(5) + 128 * l.getElement(6);
928                spd = l.getElement(8);
929                if (loconetProtocol == LnConstants.LOCONETPROTOCOL_UNKNOWN) {
930                    // it has to be 2 
931                    loconetProtocol = LnConstants.LOCONETPROTOCOL_TWO;
932                }
933                dirf = l.getElement(10) & 0b00111111;
934                id = l.getElement(18) + 128 * l.getElement(19);
935                expandedThrottleControllingID = l.getElement(18);
936                snd = snd & 0b11111100;
937                snd = snd |  ( (l.getElement(11) & 0b01100000) >> 5) ;
938                snd = l.getElement(11) & 0b00001111;
939                trk = l.getElement(7);
940                localF9  = ((l.getElement(11) & 0b00010000 ) != 0);
941                localF10 = ((l.getElement(11) & 0b00100000 ) != 0);
942                localF11 = ((l.getElement(11) & 0b01000000 ) != 0);
943                localF12 = ((l.getElement(9)  & 0b00010000 ) != 0);
944                localF13 = ((l.getElement(12) & 0b00000001 ) != 0);
945                localF14 = ((l.getElement(12) & 0b00000010 ) != 0);
946                localF15 = ((l.getElement(12) & 0b00000100 ) != 0);
947                localF16 = ((l.getElement(12) & 0b00001000 ) != 0);
948                localF17 = ((l.getElement(12) & 0b00010000 ) != 0);
949                localF18 = ((l.getElement(12) & 0b00100000 ) != 0);
950                localF19 = ((l.getElement(12) & 0b01000000 ) != 0);
951                localF20 = ((l.getElement(9)  & 0b00100000 ) != 0);
952                localF21 = ((l.getElement(13) & 0b00000001 ) != 0);
953                localF22 = ((l.getElement(13) & 0b00000010 ) != 0);
954                localF23 = ((l.getElement(13) & 0b00000100 ) != 0);
955                localF24 = ((l.getElement(13) & 0b00001000 ) != 0);
956                localF25 = ((l.getElement(13) & 0b00010000 ) != 0);
957                localF26 = ((l.getElement(13) & 0b00100000 ) != 0);
958                localF27 = ((l.getElement(13) & 0b01000000 ) != 0);
959                localF28 = ((l.getElement(9)  & 0b01000000 ) != 0);
960                leadSlot = (((l.getElement(9) & 0x03)   * 128) + l.getElement(8) );
961                notifySlotListeners();
962                break;
963            case LnConstants.OPC_SL_RD_DATA:
964                lastUpdateTime = System.currentTimeMillis();
965            //fall through
966            case LnConstants.OPC_WR_SL_DATA: {
967                if (l.getElement(1) != 0x0E) {
968                    return;  // not an appropriate reply
969                }            // valid, so fill contents
970                if (slot != l.getElement(2)) {
971                    log.error("Asked to handle message not for this slot ({}) {}", slot, l);
972                }
973                if (loconetProtocol == LnConstants.LOCONETPROTOCOL_UNKNOWN) {
974                    loconetProtocol = LnConstants.LOCONETPROTOCOL_ONE;   // all it can be...
975                }
976                stat = l.getElement(3);
977                _pcmd = l.getElement(4);
978                addr = l.getElement(4) + 128 * l.getElement(9);
979                spd = l.getElement(5);
980                dirf = l.getElement(6);
981                trk = l.getElement(7);
982                ss2 = l.getElement(8);
983                // item 9 is in add2
984                snd = l.getElement(10);
985                id = l.getElement(11) + 128 * l.getElement(12);
986                expandedThrottleControllingID = l.getElement(11);
987
988                notifySlotListeners();
989                return;
990            }
991            case LnConstants.OPC_SLOT_STAT1:
992                if (slot != l.getElement(1)) {
993                    log.error("Asked to handle message not for this slot {}", l);
994                }
995                stat = l.getElement(2);
996                notifySlotListeners();
997                lastUpdateTime = System.currentTimeMillis();
998                return;
999            case LnConstants.OPC_LOCO_SND: {
1000                // set sound functions in slot - first, clear bits
1001                snd &= ~(LnConstants.SND_F5 | LnConstants.SND_F6
1002                        | LnConstants.SND_F7 | LnConstants.SND_F8);
1003                // and set them as masked
1004                snd |= ((LnConstants.SND_F5 | LnConstants.SND_F6
1005                        | LnConstants.SND_F7 | LnConstants.SND_F8) & l.getElement(2));
1006                notifySlotListeners();
1007                lastUpdateTime = System.currentTimeMillis();
1008                return;
1009            }
1010            case LnConstants.OPC_LOCO_DIRF: {
1011                // When slot is consist-mid or consist-sub, this LocoNet Opcode
1012                // can only change the functions; direction cannot be changed.
1013                if (((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_MID) ||
1014                        ((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_SUB)) {
1015                    // set functions in slot - first, clear bits, preserving DIRF_DIR bit
1016                    dirf &= LnConstants.DIRF_DIR | (~(LnConstants.DIRF_F0
1017                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1018                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4));
1019                    // and set the function bits from the LocoNet message
1020                    dirf += ((LnConstants.DIRF_F0
1021                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1022                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2));
1023                } else {
1024                    // set direction, functions in slot - first, clear bits
1025                    dirf &= ~(LnConstants.DIRF_DIR | LnConstants.DIRF_F0
1026                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1027                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4);
1028                    // and set them as masked
1029                    dirf += ((LnConstants.DIRF_DIR | LnConstants.DIRF_F0
1030                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1031                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2));
1032
1033                }
1034                notifySlotListeners();
1035                lastUpdateTime = System.currentTimeMillis();
1036                return;
1037            }
1038            case LnConstants.OPC_MOVE_SLOTS:
1039            case LnConstants.OPC_LINK_SLOTS:
1040            case LnConstants.OPC_UNLINK_SLOTS: {
1041                // change in slot status, if any, will be reported by the reply,
1042                // so don't need to do anything here (but could)
1043                lastUpdateTime = System.currentTimeMillis();
1044                notifySlotListeners();
1045                return;
1046            }
1047            case LnConstants.OPC_LOCO_SPD: {
1048                // This opcode has no effect on the slot's speed setting if the
1049                // slot is mid-consist or sub-consist.
1050                if (((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_MID) &&
1051                        ((stat & LnConstants.CONSIST_MASK) != LnConstants.CONSIST_SUB)) {
1052
1053                    spd = l.getElement(2);
1054                    notifySlotListeners();
1055                    lastUpdateTime = System.currentTimeMillis();
1056                } else {
1057                    log.info("Ignoring speed change for slot {} marked as consist-mid or consist-sub.", slot);
1058                }
1059                return;
1060            }
1061            case LnConstants.OPC_CONSIST_FUNC: {
1062                // This opcode can be sent to a slot which is marked as mid-consist
1063                // or sub-consist.  Do not pay attention to this message if the
1064                // slot is not mid-consist or sub-consist.
1065                if (((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_MID) ||
1066                        ((stat & LnConstants.CONSIST_MASK) == LnConstants.CONSIST_SUB)) {
1067                    // set functions in slot - first, clear bits, preserving DIRF_DIR bit
1068                    dirf &= LnConstants.DIRF_DIR | (~(LnConstants.DIRF_F0
1069                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1070                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4));
1071                    // and set the function bits from the LocoNet message
1072                    dirf += ((LnConstants.DIRF_F0
1073                            | LnConstants.DIRF_F1 | LnConstants.DIRF_F2
1074                            | LnConstants.DIRF_F3 | LnConstants.DIRF_F4) & l.getElement(2));
1075                    notifySlotListeners();
1076                    lastUpdateTime = System.currentTimeMillis();
1077                }
1078                return;
1079            }
1080            case LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL: {
1081                if (l.getElement(1) == LnConstants.RE_IB2_SPECIAL_FUNCS_TOKEN) {
1082                    // IB function message
1083                    int data = l.getElement(4);
1084                    switch (l.getElement(3)) {
1085                        case LnConstants.RE_IB1_SPECIAL_F5_F11_TOKEN:
1086                            // under 8 are kept in the slot, not local variables
1087                            localF9 = ((data & LnConstants.RE_IB1_F9_MASK) != 0);
1088                            localF10 = ((data & LnConstants.RE_IB1_F10_MASK) != 0);
1089                            localF11 = ((data & LnConstants.RE_IB1_F11_MASK) != 0);
1090                            return;
1091                        case LnConstants.RE_IB2_SPECIAL_F13_F19_TOKEN:
1092                            localF13 = ((data & LnConstants.RE_IB2_F13_MASK) != 0);
1093                            localF14 = ((data & LnConstants.RE_IB2_F14_MASK) != 0);
1094                            localF15 = ((data & LnConstants.RE_IB2_F15_MASK) != 0);
1095                            localF16 = ((data & LnConstants.RE_IB2_F16_MASK) != 0);
1096                            localF17 = ((data & LnConstants.RE_IB2_F17_MASK) != 0);
1097                            localF18 = ((data & LnConstants.RE_IB2_F18_MASK) != 0);
1098                            localF19 = ((data & LnConstants.RE_IB2_F19_MASK) != 0);
1099                            return;
1100                        case LnConstants.RE_IB2_SPECIAL_F21_F27_TOKEN:
1101                            localF21 = ((data & LnConstants.RE_IB2_F21_MASK) != 0);
1102                            localF22 = ((data & LnConstants.RE_IB2_F22_MASK) != 0);
1103                            localF23 = ((data & LnConstants.RE_IB2_F23_MASK) != 0);
1104                            localF24 = ((data & LnConstants.RE_IB2_F24_MASK) != 0);
1105                            localF25 = ((data & LnConstants.RE_IB2_F25_MASK) != 0);
1106                            localF26 = ((data & LnConstants.RE_IB2_F26_MASK) != 0);
1107                            localF27 = ((data & LnConstants.RE_IB2_F27_MASK) != 0);
1108                            return;
1109                        case LnConstants.RE_IB2_SPECIAL_F20_F28_TOKEN:
1110                            localF12 = ((data & LnConstants.RE_IB2_SPECIAL_F12_MASK) != 0);
1111                            localF20 = ((data & LnConstants.RE_IB2_SPECIAL_F20_MASK) != 0);
1112                            localF28 = ((data & LnConstants.RE_IB2_SPECIAL_F28_MASK) != 0);
1113                            return;
1114                        default:
1115                            log.debug("Found IB RE_OPC_IB2_SPECIAL message of {}", l);
1116                            return;
1117                    }
1118                }
1119                int src = slot;
1120                int dest = ((l.getElement(3) & 0x07) * 128) + (l.getElement(4) & 0x7f);
1121                // null move or change status or consisting or?
1122                if ((l.getElement(1) & 0b11111000) == 0b00111000) {
1123                    if (((l.getElement(3) & 0b01110000) == 0b01100000)) {
1124                        stat = l.getElement(4);
1125                        notifySlotListeners();
1126                        return;
1127                    } else if ((l.getElement(3) & 0b01110000) == 0b01010000) {
1128                        // unconsisting returns slot contents so do nothing to this slot
1129                        return;
1130                    } else if ((l.getElement(3) & 0b01110000) == 0b01000000) {
1131                        //consisting do something?
1132                        //Set From slot as slave to slot as master
1133                        stat = stat | LnConstants.CONSIST_TOP;
1134                        notifySlotListeners();
1135                        return;
1136                    } else if (src == 0 && dest == 0) {
1137                        stat = stat & ~LnConstants.LOCO_IN_USE;
1138                        log.debug("set idle");
1139                        notifySlotListeners();
1140                        return;
1141                    }
1142                }
1143                return;
1144            }
1145            default: {
1146                throw new LocoNetException("message can't be parsed"); // NOI18N
1147            }
1148        }
1149    }
1150
1151    /**
1152     * Sets F9 through F28 (as appropriate) from data extracted from LocoNet
1153     * "OPC_IMM_PACKET" message.
1154     * <p>If the pkt parameter does not contain data from an appropriate
1155     * OPC_IMM_PACKET message, the pkt is ignored and the slot object remains
1156     * unchanged.
1157     *
1158     * @param pkt is a "long" consisting of four bytes extracted from a LocoNet
1159     * "OPC_IMM_PACKET" message.
1160     * <p>
1161     * {@link jmri.jmrix.loconet.SlotManager#getDirectDccPacket(LocoNetMessage m)}
1162     */
1163    public void functionMessage(long pkt) {
1164        // parse for which set of functions
1165        if ((pkt & 0xFFFFFF0) == 0xA0) {
1166            // F9-12
1167            localF9 = ((pkt & 0x01) != 0);
1168            localF10 = ((pkt & 0x02) != 0);
1169            localF11 = ((pkt & 0x04) != 0);
1170            localF12 = ((pkt & 0x08) != 0);
1171            notifySlotListeners();
1172        } else if ((pkt & 0xFFFFFF00) == 0xDE00) {
1173            // check F13-20
1174            localF13 = ((pkt & 0x01) != 0);
1175            localF14 = ((pkt & 0x02) != 0);
1176            localF15 = ((pkt & 0x04) != 0);
1177            localF16 = ((pkt & 0x08) != 0);
1178            localF17 = ((pkt & 0x10) != 0);
1179            localF18 = ((pkt & 0x20) != 0);
1180            localF19 = ((pkt & 0x40) != 0);
1181            localF20 = ((pkt & 0x80) != 0);
1182            notifySlotListeners();
1183        } else if ((pkt & 0xFFFFFF00) == 0xDF00) {
1184            // check F21-28
1185            localF21 = ((pkt & 0x01) != 0);
1186            localF22 = ((pkt & 0x02) != 0);
1187            localF23 = ((pkt & 0x04) != 0);
1188            localF24 = ((pkt & 0x08) != 0);
1189            localF25 = ((pkt & 0x10) != 0);
1190            localF26 = ((pkt & 0x20) != 0);
1191            localF27 = ((pkt & 0x40) != 0);
1192            localF28 = ((pkt & 0x80) != 0);
1193            notifySlotListeners();
1194        }
1195    }
1196
1197    /**
1198     * Update the decoder type bits in STAT1 (D2, D1, D0)
1199     *
1200     * @param status New values for STAT1 (D2, D1, D0)
1201     * @return Formatted LocoNet message to change value.
1202     */
1203    public LocoNetMessage writeMode(int status) {
1204        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) {
1205            LocoNetMessage l = new LocoNetMessage(4);
1206            l.setOpCode(LnConstants.OPC_SLOT_STAT1);
1207            l.setElement(1, slot);
1208            l.setElement(2, (stat & ~LnConstants.DEC_MODE_MASK) | status);
1209            return l;
1210        } else {
1211            LocoNetMessage l = new LocoNetMessage(6);
1212            l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL);
1213            l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ;
1214            l.setElement(2, slot & 0x7f);
1215            l.setElement(3, 0x60);
1216            l.setElement(4, (stat & ~LnConstants.DEC_MODE_MASK) | status);
1217            return l;
1218        }
1219    }
1220
1221    /**
1222     * Sets the object's ID value and returns a LocoNet message to inform the
1223     * command station that the throttle ID has been changed.
1224     * @param newID  the new ID number to set into the slot object
1225     * @return a LocoNet message containing a "Slot Write" message to inform the
1226     * command station that a specific throttle is controlling the slot.
1227     */
1228    public LocoNetMessage writeThrottleID(int newID) {
1229        id = (newID & 0x17F);
1230        return writeSlot();
1231    }
1232
1233    /**
1234     * Set the throttle ID in the slot
1235     *
1236     * @param throttleId full id
1237     */
1238    public void setThrottleIdentity(int throttleId) {
1239        id = throttleId;
1240    }
1241
1242    /**
1243     * Get the throttle ID in the slot
1244     *
1245     *@return the Id of the Throttle
1246     */
1247    public int getThrottleIdentity() {
1248        return id;
1249    }
1250
1251    public int getLeadSlot() {
1252        return leadSlot;
1253    }
1254
1255    /**
1256     * Update the status mode bits in STAT1 (D5, D4)
1257     *
1258     * @param status New values for STAT1 (D5, D4)
1259     * @return Formatted LocoNet message to change value.
1260     */
1261    public LocoNetMessage writeStatus(int status) {
1262        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO ) {
1263            LocoNetMessage l = new LocoNetMessage(4);
1264            l.setOpCode(LnConstants.OPC_SLOT_STAT1);
1265            l.setElement(1, slot);
1266            l.setElement(2, (stat & ~LnConstants.LOCOSTAT_MASK) | status);
1267            return l;
1268        } else {
1269            LocoNetMessage l = new LocoNetMessage(6);
1270            l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL);
1271            l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ;
1272            l.setElement(2, slot & 0x7f);
1273            l.setElement(3, 0x60);
1274            l.setElement(4, (stat & ~LnConstants.LOCOSTAT_MASK) | status);
1275            return l;
1276        }
1277    }
1278
1279    /**
1280     * Update Speed
1281     *
1282     * @param speed new speed
1283     * @return Formatted LocoNet message to change value.
1284     */
1285    public LocoNetMessage writeSpeed(int speed) {
1286        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) {
1287            LocoNetMessage l = new LocoNetMessage(4);
1288            l.setOpCode(LnConstants.OPC_LOCO_SPD);
1289            l.setElement(1, slot );
1290            l.setElement(2, speed);
1291            return l;
1292        } else {
1293            LocoNetMessage l = new LocoNetMessage(6);
1294            l.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR);
1295            l.setElement(1, ((slot / 128) & 0x03) | ((dirf &  LnConstants.DIRF_DIR ) >> 2) );
1296            l.setElement(2, slot & 0x7f);
1297            l.setElement(3, (id & 0x7f));
1298            l.setElement(4, speed);
1299            return l;
1300        }
1301    }
1302
1303    /**
1304     * Create LocoNet message which dispatches this slot
1305     *
1306     * Note that the invoking method ought to invoke the slot's NotifySlotListeners
1307     * method to inform any other interested parties that the slot status has changed.
1308     *
1309     * @return LocoNet message which "dispatches" the slot
1310    */
1311    public LocoNetMessage dispatchSlot() {
1312        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) {
1313            LocoNetMessage l = new LocoNetMessage(4);
1314            l.setOpCode(LnConstants.OPC_MOVE_SLOTS);
1315            l.setElement(1, slot);
1316            l.setElement(2, 0);
1317            return l;
1318        } else {
1319            LocoNetMessage l = new LocoNetMessage(6);
1320            l.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL);
1321            l.setElement(1, ((slot / 128) & 0x03) | 0b00111000 ) ;
1322            l.setElement(2, slot & 0x7f);
1323            l.setElement(3, 0);
1324            l.setElement(4, 0);
1325            return l;
1326        }
1327    }
1328
1329    /**
1330     * Create a message to perform a null move on this slot.
1331     * @return correct LocoNetMessage for protocol being used.
1332     */
1333    public LocoNetMessage writeNullMove() {
1334        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO) {
1335            // perform the null slot move for low numbered slots
1336            LocoNetMessage msg = new LocoNetMessage(4);
1337            msg.setOpCode(LnConstants.OPC_MOVE_SLOTS);
1338            msg.setElement(1, slot);
1339            msg.setElement(2, slot);
1340            return (msg);
1341        }
1342        // or the null move for higher numbered slots
1343        LocoNetMessage msg = new LocoNetMessage(6);
1344        msg.setOpCode(0xd4);
1345        msg.setElement(1, (slot / 128) | 0b00111000);
1346        msg.setElement(2, slot & 0b01111111);
1347        msg.setElement(3, (slot / 128) & 0b00000111);
1348        msg.setElement(4, slot & 0b01111111);
1349        return msg;
1350    }
1351
1352    /**
1353     * Create a LocoNet OPC_SLOT_STAT1 message which releases this slot to the
1354     * "Common" state
1355     *
1356     * The invoking method must send the returned LocoNet message to LocoNet in
1357     * order to have a useful effect.
1358     *
1359     * Upon receipt of the echo of the transmitted OPC_SLOT_STAT1 message, the
1360     * LocoNetSlot object will notify its listeners.
1361     *
1362     * @return LocoNet message which "releases" the slot to the "Common" state
1363    */
1364    public LocoNetMessage releaseSlot() {
1365        return writeStatus(LnConstants.LOCO_COMMON);
1366    }
1367
1368    /**
1369     * Creates a LocoNet "OPC_WR_SL_DATA" message containing the current state of
1370     * the LocoNetSlot object.
1371     *
1372     * @return a LocoNet message which can be used to inform the command station
1373     * of a change in the slot contents.
1374     */
1375    public LocoNetMessage writeSlot() {
1376        if (loconetProtocol != LnConstants.LOCONETPROTOCOL_TWO || slot == LnConstants.FC_SLOT) { //special case for fc
1377            LocoNetMessage l = new LocoNetMessage(14);
1378            l.setOpCode(LnConstants.OPC_WR_SL_DATA);
1379            l.setElement(1, 0x0E);
1380            l.setElement(2, slot & 0x7F);
1381            l.setElement(3, stat & 0x7F);
1382            l.setElement(4, addr & 0x7F);
1383            l.setElement(9, (addr / 128) & 0x7F);
1384            l.setElement(5, spd & 0x7F);
1385            l.setElement(6, dirf & 0x7F);
1386            l.setElement(7, trk & 0x7F);
1387            l.setElement(8, ss2 & 0x7F);
1388            // item 9 is add2
1389            l.setElement(10, snd & 0x7F);
1390            l.setElement(11, id & 0x7F);
1391            l.setElement(12, (id / 128) & 0x7F);
1392            return l;
1393        }
1394        LocoNetMessage l = new LocoNetMessage(21);
1395        l.setOpCode(LnConstants.OPC_EXP_WR_SL_DATA);
1396        l.setElement(1, 0x15);
1397        l.setElement(2, (slot / 128) & 0x03);
1398        l.setElement(3, slot & 0x7F);
1399        l.setElement(4, stat & 0x7F);
1400        l.setElement(6, (addr / 128) & 0x7F);
1401        l.setElement(5, addr & 0x7F);
1402        l.setElement(7, ( trk | 0x40 ) & 0x7F);  // track power status and Expanded slot protocol
1403        l.setElement(8, spd & 0x7F);
1404        l.setElement(9, (isF12() ? 0b00010000 : 0x00 )
1405                | (isF20() ? 0b00100000 : 0x00)
1406                | (isF28() ? 0b01000000 : 0x00));
1407        l.setElement(10, ( isForward() ? 0x00 : 0x00100000)
1408                | (isF0() ? 0b00010000 : 0x00)
1409                | (isF1() ? 0b00000001 : 0x00)
1410                | (isF2() ? 0b00000010 : 0x00)
1411                | (isF3() ? 0b00000100 : 0x00)
1412                | (isF4() ? 0b00001000 : 0x00));
1413        l.setElement(11, ( isF5() ? 0b00000001 : 0x00)
1414                | ( isF6() ? 0b00000010 : 0x00)
1415                | ( isF7() ? 0b00000100 : 0x00)
1416                | ( isF8() ? 0b00001000 : 0x00)
1417                | ( isF9() ? 0b00010000 : 0x00)
1418                | (isF10() ? 0b00100000 : 0x00)
1419                | (isF11() ? 0b01000000 : 0x00));
1420        l.setElement(12,( isF13() ? 0b00000001 : 0x00)
1421                | (isF14() ? 0b00000010 : 0x00)
1422                | (isF15() ? 0b00000100 : 0x00)
1423                | (isF16() ? 0b00001000 : 0x00)
1424                | (isF17() ? 0b00010000 : 0x00)
1425                | (isF18() ? 0b00100000 : 0x00)
1426                | (isF19() ? 0b01000000 : 0x00));
1427        l.setElement(13,( isF21() ? 0b00000001 : 0x00)
1428                | (isF22() ? 0b00000010 : 0x00)
1429                | (isF23() ? 0b00000100 : 0x00)
1430                | (isF24() ? 0b00001000 : 0x00)
1431                | (isF25() ? 0b00010000 : 0x00)
1432                | (isF26() ? 0b00100000 : 0x00)
1433                | (isF27() ? 0b01000000 : 0x00));
1434        l.setElement(18, id & 0x7F);
1435        l.setElement(19, (id / 128) & 0x7F);
1436        return l;
1437    }
1438
1439    // data values to echo slot contents
1440    final private int slot;   // <SLOT#> is the number of the slot that was read.
1441    private boolean isInitialized; // set when full initilization is complete with the throttle ID.
1442    private int loconetProtocol; // protocol used by the slot.
1443    private SlotType slotType; // system, loco, unknown
1444    private int stat; // <STAT> is the status of the slot
1445    private int addr; // full address of the loco, made from
1446    //    <ADDR> is the low 7 (0-6) bits of the Loco address
1447    //    <ADD2> is the high 7 bits (7-13) of the 14-bit loco address
1448    private int spd; // <SPD> is the current speed (0-127)
1449    private int dirf; // <DIRF> is the current Direction and the setting for functions F0-F4
1450    private int trk = 7; // <TRK> is the global track status
1451    private int ss2; // <SS2> is the an additional slot status
1452    private int snd;  // <SND> is the settings for functions F5-F8
1453    private int id;  // throttle id, made from
1454    //     <ID1> and <ID2> normally identify the throttle controlling the loco
1455    private int expandedThrottleControllingID; //the throttle ID byte that is used in sending commands that require a throttle ID. (ID1)
1456    private int leadSlot; // the top slot for this slot in a consist.
1457
1458    private int _pcmd;  // hold pcmd and pstat for programmer
1459
1460    private long lastUpdateTime; // Time of last update for detecting stale slots
1461
1462    // data members to hold contact with the slot listeners
1463    final private List<SlotListener> slotListeners = new ArrayList<>();
1464
1465    /**
1466     * Registers a slot listener if it is not already registered.
1467     *
1468     * @param l  a slot listener
1469     */
1470    public synchronized void addSlotListener(SlotListener l) {
1471        // add only if not already registered
1472        if (!slotListeners.contains(l)) {
1473            slotListeners.add(l);
1474        }
1475    }
1476
1477    /**
1478     * Un-registers a slot listener.
1479     *
1480     * @param l  a slot listener
1481     */
1482    public synchronized void removeSlotListener(SlotListener l) {
1483        if (slotListeners.contains(l)) {
1484            slotListeners.remove(l);
1485        }
1486    }
1487
1488    /**
1489     * Returns the timestamp when this LocoNetSlot was updated by some LocoNet
1490     * message.
1491     *
1492     * @return last time the slot info was updated
1493     */
1494    public long getLastUpdateTime() {
1495        return lastUpdateTime;
1496    }
1497
1498    /**
1499     * Notifies all listeners that this slot has been changed in some way.
1500     */
1501    public void notifySlotListeners() {
1502        // make a copy of the listener list to synchronized not needed for transmit
1503        List<SlotListener> v;
1504        synchronized (this) {
1505            v = new ArrayList<>(slotListeners);
1506        }
1507        log.debug("notify {} SlotListeners",v.size()); // NOI18N
1508        // forward to all listeners
1509        int cnt = v.size();
1510        for (int i = 0; i < cnt; i++) {
1511            SlotListener client = v.get(i);
1512            client.notifyChangedSlot(this);
1513        }
1514    }
1515
1516    /**
1517     * For fast-clock slot, set a "CLK_CNTRL" bit On. This method logs an error
1518     * if invoked for a slot other than the fast-clock slot.
1519     *
1520     * @param val is the new "CLK_CNTRL" bit value to turn On
1521     */
1522    public void setFcCntrlBitOn(int val) {
1523        // TODO: consider throwing a LocoNetException if issued for a slot other
1524        // than the "fast clock slot".
1525        if (getSlot() != LnConstants.FC_SLOT) {
1526            log.error("setFcCntrl invalid for slot [{}]", getSlot());
1527        }
1528        snd |= val;
1529    }
1530
1531    /**
1532     * For fast-clock slot, set a "CLK_CNTRL" bit Off. This method logs an error
1533     * if invoked for a slot other than the fast-clock slot.
1534     *
1535     * @param val is the new "CLK_CNTRL" bit value to turn Off
1536     */
1537    public void setFcCntrlBitOff(int val) {
1538        // TODO: consider throwing a LocoNetException if issued for a slot other
1539        // than the "fast clock slot".
1540        if (getSlot() != LnConstants.FC_SLOT) {
1541            log.error("setFcCntrl invalid for slot [{}]" , getSlot());
1542        }
1543        snd &= ~val;
1544    }
1545
1546    /**
1547     * Get the track status byte (location 7)
1548     * <p>
1549     * Note that the &lt;TRK&gt; byte is not accurate on some command stations.
1550     *
1551     * @return the effective &lt;TRK&gt; byte
1552     */
1553    public int getTrackStatus() { return trk; }
1554
1555    /**
1556     * Set the track status byte (location 7)
1557     * <p>
1558     * Note that setting the LocoNetSlot object's track status may result in a
1559     * change to the command station's actual track status if the slot's status
1560     * is communicated to the command station via an OPC_WR_DL_DATA LocoNet message.
1561     *
1562     * @param status is the new track status value.
1563     */
1564    public void setTrackStatus(int status) { trk = status; }
1565
1566    /**
1567     * Return the days value from the slot.  Only valid for fast-clock slot.
1568     * <p>
1569     * This method logs an error if invoked for a slot other than the fast-clock slot.
1570     *
1571     * @return "Days" value currently in fast-clock slot.
1572     */
1573    public int getFcDays() {
1574        // TODO: consider throwing a LocoNetException if issued for a slot other
1575        // than the "fast clock slot".
1576        if (getSlot() != LnConstants.FC_SLOT) {
1577            log.error("getFcDays invalid for slot {}", getSlot());
1578        }
1579        return (addr & 0x3f80) / 0x80;
1580    }
1581
1582    /**
1583     * For fast-clock slot, set "days" value.
1584     * <p>
1585     * Note that the new days value is not effective until a LocoNet
1586     * message is sent which writes the fast-clock slot data.
1587     * <p>
1588     * This method logs an error if invoked for a slot other than the fast-clock slot.
1589     *
1590     * @param val is the new fast-clock "days" value
1591     */
1592    public void setFcDays(int val) {
1593        // TODO: consider throwing a LocoNetException if issued for a slot other
1594        // than the "fast clock slot".
1595        if (getSlot() != LnConstants.FC_SLOT) {
1596            log.error("setFcDays invalid for slot {}", getSlot());
1597        }
1598        addr = val * 128 + (addr & 0x7f);
1599    }
1600
1601    /**
1602     * Return the hours value from the slot.  Only valid for fast-clock slot.
1603     * <p>
1604     * This method logs an error if invoked for a slot other than the fast-clock slot.
1605     *
1606     * @return "Hours" value currently stored in fast clock slot.
1607     */
1608    public int getFcHours() {
1609        // TODO: consider throwing a LocoNetException if issued for a slot other
1610        // than the "fast clock slot".
1611        if (getSlot() != LnConstants.FC_SLOT) {
1612            log.error("getFcHours invalid for slot {}", getSlot());
1613        }
1614        int temp = ((256 - ss2) & 0x7F) % 24;
1615        return (24 - temp) % 24;
1616    }
1617
1618    /**
1619     * For fast-clock slot, set "hours" value.
1620     * <p>
1621     * Note that the new hours value is not effective until a LocoNet
1622     * message is sent which writes the fast-clock slot data.
1623     * <p>
1624     * This method logs an error if invoked for a slot other than the fast-clock slot.
1625     *
1626     * @param val is the new fast-clock "hours" value
1627     */
1628    public void setFcHours(int val) {
1629        // TODO: consider throwing a LocoNetException if issued for a slot other
1630        // than the "fast clock slot".
1631        if (getSlot() != LnConstants.FC_SLOT) {
1632            log.error("setFcHours invalid for slot {}", getSlot());
1633        }
1634        ss2 = (256 - (24 - val)) & 0x7F;
1635    }
1636
1637    /**
1638     * Return the minutes value from the slot.  Only valid for fast-clock slot.
1639     * <p>
1640     * This method logs an error if invoked for a slot other than the fast-clock slot.
1641     *
1642     * @return Return minutes value currently stored in the fast clock slot.
1643     */
1644    public int getFcMinutes() {
1645        // TODO: consider throwing a LocoNetException if issued for a slot other
1646        // than the "fast clock slot".
1647        if (getSlot() != LnConstants.FC_SLOT) {
1648            log.error("getFcMinutes invalid for slot {}", getSlot());
1649        }
1650        int temp = ((255 - dirf) & 0x7F) % 60;
1651        return (60 - temp) % 60;
1652    }
1653
1654    /**
1655     * For fast-clock slot, set "minutes" value.
1656     * <p>
1657     * Note that the new minutes value is not effective until a LocoNet
1658     * message is sent which writes the fast-clock slot data.
1659     * <p>
1660     * This method logs an error if invoked for a slot other than the fast-clock slot.
1661     *
1662     * @param val is the new fast-clock "minutes" value
1663     */
1664    public void setFcMinutes(int val) {
1665        // TODO: consider throwing a LocoNetException if issued for a slot other
1666        // than the "fast clock slot".
1667        if (getSlot() != LnConstants.FC_SLOT) {
1668            log.error("setFcMinutes invalid for slot {}", getSlot());
1669        }
1670        dirf = (255 - (60 - val)) & 0x7F;
1671    }
1672
1673    /**
1674     * Return the fractional minutes value from the slot.  Only valid for fast-
1675     * clock slot.
1676     * <p>
1677     * This method logs an error if invoked for a slot other than the fast-clock slot.
1678     *
1679     * @return Return frac_mins field which is the number of 65ms ticks until
1680     *         then next minute rollover. These ticks step at the current fast
1681     *         clock rate
1682     */
1683    public int getFcFracMins() {
1684        // TODO: consider throwing a LocoNetException if issued for a slot other
1685        // than the "fast clock slot".
1686        if (getSlot() != LnConstants.FC_SLOT) {
1687            log.error("getFcFracMins invalid for slot {}", getSlot());
1688        }
1689        return ((addr & 0x7F) | ((spd & 0x7F) << 8));
1690    }
1691
1692    /**
1693     * Set the "frac_mins" value.
1694     * This has to be calculated as required by the Command Station,
1695     * then bit shifted if required.
1696     * It is comprised of a base number and the distance from the base to 0x8000
1697     * or 0x4000 deoending on command station.
1698     * It is read and written as is LO,HO and loses the bit 7 of the LO.
1699     * It was never intended for external use.
1700     * The base can be found by setting the clock to 0xXX7F, with a rate of 1
1701     * and pounding the clock every 250 to 100 msecs until it roles.
1702     * <p>
1703     * Note 1: The new fractional minutes value is not effective until a LocoNet slot write happens
1704     * <p>
1705     * Note 2: DT40x &amp; DT500 throttles ignore this value, and set only the whole minutes.
1706     * <p>
1707     * This method logs an error if invoked for a slot other than the fast-clock slot.
1708     * @param val is the new fast-clock "fractional minutes" including the base, and bit shifted if required.
1709     */
1710    public void setFcFracMins(int val) {
1711        // TODO: consider throwing a LocoNetException if issued for a slot other
1712        // than the "fast clock slot".
1713        if (getSlot() != LnConstants.FC_SLOT) {
1714            log.error("setFcFracMins invalid for slot {}", getSlot());
1715        }
1716        int temp = 0x7F7F & val;
1717        addr = (addr & 0x7F00) | (temp & 0x7F);
1718        spd = (temp >> 8) & 0x7F;
1719    }
1720
1721    /**
1722     * Get the fast-clock rate.  Only valid for fast-clock slot.
1723     * <p>
1724     * This method logs an error if invoked for a slot other than the fast-clock slot.
1725     *
1726     * @return Rate stored in fast clock slot.
1727     */
1728    public int getFcRate() {
1729        // TODO: consider throwing a LocoNetException if issued for a slot other
1730        // than the "fast clock slot".
1731        if (getSlot() != LnConstants.FC_SLOT) {
1732            log.error("getFcRate invalid for slot {}", getSlot());
1733        }
1734        return stat;
1735    }
1736
1737    /**
1738     * For fast-clock slot, set "rate" value.
1739     * <p>
1740     * Note that the new rate is not effective until a LocoNet message is sent
1741     * which writes the fast-clock slot data.
1742     * <p>
1743     * This method logs an error if invoked for a slot other than the fast-clock slot.
1744     *
1745     * @param val is the new fast-clock rate
1746     */
1747    public void setFcRate(int val) {
1748        // TODO: consider throwing a LocoNetException if issued for a slot other
1749        // than the "fast clock slot".
1750        if (getSlot() != LnConstants.FC_SLOT) {
1751            log.error("setFcRate invalid for slot {}", getSlot());
1752        }
1753        stat = val & 0x7F;
1754    }
1755
1756    private final static Logger log = LoggerFactory.getLogger(LocoNetSlot.class);
1757}