/
XNetTurnout.java
845 lines (787 loc) · 35 KB
/
XNetTurnout.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
package jmri.jmrix.lenz;
import jmri.implementation.AbstractTurnout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.concurrent.GuardedBy;
import java.util.concurrent.LinkedBlockingQueue;
/**
* Extend jmri.AbstractTurnout for XNet layouts
* <p>
* Turnout opperation on XpressNet based systems goes through the following
* sequence:
* <ul>
* <li> set the commanded state, and, Send request to command station to start
* sending DCC operations packet to track</li>
* <li> Wait for response message from command station. (valid response list
* follows)</li>
* <li> Send request to command station to stop sending DCC operations packet to
* track</li>
* <li> Wait for response from command station
* <ul>
* <li>If Success Message, set Known State to Commanded State</li>
* <li>If error message, repeat previous step</li>
* </ul>
* </li>
* </ul>
* <p>
* NOTE: Some XpressNet Command stations take no action when the message
* generated during the third step is received.
* <p>
* Valid response messages are command station dependent, but there are 4
* possibilities:
* <ul>
* <li> a "Command Successfully Received..." (aka "OK") message</li>
* <li> a "Feedback Response Message" indicating the message is for a turnout
* with feedback</li>
* <li> a "Feedback Response Message" indicating the message is for a turnout
* without feedback</li>
* <li> The XpressNet protocol allows for no response. </li>
* </ul>
* <p>
* Response NOTE 1: The "Command Successfully Received..." message is generated
* by the lenz LIxxx interfaces when it successfully transfers the command to
* the command station. When this happens, the command station generates no
* useable response message.
* <p>
* Response NOTE 2: Currently the only command stations known to generate
* Feedback response messages are the Lenz LZ100 and LZV100.
* <p>
* Response NOTE 3: Software version 3.2 and above LZ100 and LZV100 may send
* either a Feedback response or no response at all. All other known command
* stations generate no response.
* <p>
* Response NOTE 4: The Feedback response messages may be generated
* asynchronously
* <p>
* Response NOTE 5: Feedback response messages may contain feedback for more
* than one device. The devices included in the response may or may not be
* stationary decoders (they can also be feedback encoders see
* {@link XNetSensor}).
* <p>
* Response NOTE 6: The last situation situation is not currently handled. The
* supported interfaces garantee at least an "OK" message will be sent to the
* computer
* <p>
* What is done with each of the response messages depends on which feedback
* mode is in use. "DIRECT,"MONITORING", and "EXACT" feedback mode are supported
* directly by this class.
* <p>
* "DIRECT" mode instantly triggers step 3 when any valid response message for
* this turnout is received from the command station or computer interface.
* <p>
* "SIGNAL" mode is identical to "DIRECT" mode, except it skips step 2. i.e. it
* triggers step 3 without receiving any reply from the command station.
* <p>
* "MONITORING" mode is an extention to direct mode. In monitoring mode, a
* feedback response message (for a turnout with or without feedback) is
* interpreted to set the known state of the turnout based on information
* provided by the command station.
* <p>
* "MONITORING" mode will interpret the feedback response messages when they are
* generated by external sources (fascia controls or other XpressNet devices)
* and that information is received by the computer.
* <p>
* "EXACT" mode is an extention of "MONITORING" mode. In addition to
* interpretting all feedback messages from the command station, "EXACT" mode
* will monitor the "motion complete" bit of the feedback response.
* <p>
* For turnouts without feedback, the motion complete bit is always set, so
* "EXACT" mode handles these messages as though the specified feedback mode is
* "MONITORING" mode.
* <p>
* For turnouts with feedback, "EXACT" mode polls the command station until the
* motion complete bit is set before triggering step 3 of the turnout operation
* sequence.
* <p>
* "EXACT" mode will interpret the feedback response messages when they are
* generated by external sources (fascia controls or other XpressNet devices)
* and that information is received by the computer.
* <p>
* NOTE: For LZ100 and LZV100 command stations prior to version 3.2, it may be
* necessary to poll for the feedback response data.
*
* @author Bob Jacobsen Copyright (C) 2001
* @author Paul Bender Copyright (C) 2003-2010
*/
public class XNetTurnout extends AbstractTurnout implements XNetListener {
/* State information */
protected static final int OFFSENT = 1;
protected static final int COMMANDSENT = 2;
protected static final int STATUSREQUESTSENT = 4;
protected static final int IDLE = 0;
protected int internalState = IDLE;
/* Static arrays to hold Lenz specific feedback mode information */
static String[] modeNames = null;
static int[] modeValues = null;
@GuardedBy("this")
protected int _mThrown = jmri.Turnout.THROWN;
@GuardedBy("this")
protected int _mClosed = jmri.Turnout.CLOSED;
protected String _prefix = "X"; // default
protected XNetTrafficController tc = null;
public XNetTurnout(String prefix, int pNumber, XNetTrafficController controller) { // a human-readable turnout number must be specified!
super(prefix + "T" + pNumber);
tc = controller;
_prefix = prefix;
mNumber = pNumber;
requestList = new LinkedBlockingQueue<>();
/* Add additiona feedback types information */
_validFeedbackTypes |= MONITORING | EXACT | SIGNAL;
// Default feedback mode is MONITORING
_activeFeedbackType = MONITORING;
setModeInformation(_validFeedbackNames, _validFeedbackModes);
// set the mode names and values based on the static values.
_validFeedbackNames = getModeNames();
_validFeedbackModes = getModeValues();
// Register to get property change information from the superclass
_stateListener = new XNetTurnoutStateListener(this);
this.addPropertyChangeListener(_stateListener);
// Finally, request the current state from the layout.
tc.getFeedbackMessageCache().requestCachedStateFromLayout(this);
}
/**
* Set the mode information for XpressNet Turnouts.
*/
private static synchronized void setModeInformation(String[] feedbackNames, int[] feedbackModes) {
// if it hasn't been done already, create static arrays to hold
// the Lenz specific feedback information.
if (modeNames == null) {
if (feedbackNames.length != feedbackModes.length) {
log.error("int and string feedback arrays different length");
}
modeNames = new String[feedbackNames.length + 3];
modeValues = new int[feedbackNames.length + 3];
for (int i = 0; i < feedbackNames.length; i++) {
modeNames[i] = feedbackNames[i];
modeValues[i] = feedbackModes[i];
}
modeNames[feedbackNames.length] = "MONITORING";
modeValues[feedbackNames.length] = MONITORING;
modeNames[feedbackNames.length + 1] = "EXACT";
modeValues[feedbackNames.length + 1] = EXACT;
modeNames[feedbackNames.length + 2] = "SIGNAL";
modeValues[feedbackNames.length + 2] = SIGNAL;
}
}
static int[] getModeValues() {
return modeValues;
}
static String[] getModeNames() {
return modeNames;
}
public int getNumber() {
return mNumber;
}
/**
* Set the Commanded State.
* This method overides {@link jmri.implementation.AbstractTurnout#setCommandedState(int)}.
*/
@Override
public void setCommandedState(int s) {
if (log.isDebugEnabled()) {
log.debug("set commanded state for XNet turnout {} to {}", getSystemName(), s);
}
synchronized (this) {
newCommandedState(s);
}
myOperator = getTurnoutOperator(); // MUST set myOperator before starting the thread
if (myOperator == null) {
forwardCommandChangeToLayout(s);
synchronized (this) {
newKnownState(INCONSISTENT);
}
} else {
myOperator.start();
}
}
/**
* Handle a request to change state by sending an XpressNet command.
*/
@Override
protected synchronized void forwardCommandChangeToLayout(int s) {
if (s != _mClosed && s != _mThrown) {
log.warn("Turnout {}: state {} not forwarded to layout.", mNumber, s);
return;
}
// get the right packet
XNetMessage msg = XNetMessage.getTurnoutCommandMsg(mNumber,
(s & _mClosed) != 0,
(s & _mThrown) != 0,
true);
if (getFeedbackMode() == SIGNAL) {
msg.setTimeout(0); // Set the timeout to 0, so the off message can
// be sent immediately.
// leave the next line commented out for now.
// It may be enabled later to allow SIGNAL mode to ignore
// directed replies, which lets the traffic controller move on
// to the next message without waiting.
//msg.setBroadcastReply();
tc.sendXNetMessage(msg, null);
sendOffMessage();
} else {
queueMessage(msg, COMMANDSENT, this);
}
}
@Override
protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) {
log.debug("Send command to {} Pushbutton {}T{}", (_pushButtonLockout ? "Lock" : "Unlock"), _prefix, mNumber);
}
/**
* Request an update on status by sending an XpressNet message.
*/
@Override
public void requestUpdateFromLayout() {
// This will handle ONESENSOR and TWOSENSOR feedback modes.
super.requestUpdateFromLayout();
// To do this, we send an XpressNet Accessory Decoder Information
// Request.
// The generated message works for Feedback modules and turnouts
// with feedback, but the address passed is translated as though it
// is a turnout address. As a result, we substitute our base
// address in for the address. after the message is returned.
XNetMessage msg = XNetMessage.getFeedbackRequestMsg(mNumber,
((mNumber - 1) % 4) < 2);
queueMessage(msg,IDLE,null); //status is returned via the manager.
}
@Override
public synchronized void setInverted(boolean inverted) {
log.debug("Inverting Turnout State for turnout {}T{}", _prefix, mNumber);
_inverted = inverted;
if (inverted) {
_mThrown = jmri.Turnout.CLOSED;
_mClosed = jmri.Turnout.THROWN;
} else {
_mThrown = jmri.Turnout.THROWN;
_mClosed = jmri.Turnout.CLOSED;
}
super.setInverted(inverted);
}
@Override
public boolean canInvert() {
return true;
}
/**
* Package protected class which allows the Manger to send
* a feedback message at initilization without changing the state of the
* turnout with respect to whether or not a feedback request was sent. This
* is used only when the turnout is created by on layout feedback.
*/
synchronized void initmessage(XNetReply l) {
int oldState = internalState;
message(l);
internalState = oldState;
}
/**
* Handle an incoming message from the XpressNet.
*/
@Override
public synchronized void message(XNetReply l) {
log.debug("received message: {}", l);
if (internalState == OFFSENT) {
if (l.isOkMessage() && !l.isUnsolicited()) {
/* the command was successfully received */
synchronized (this) {
newKnownState(getCommandedState());
}
sendQueuedMessage();
return;
} else if (l.isRetransmittableErrorMsg()) {
return; // don't do anything, the Traffic
// Controller is handling retransmitting
// this one.
} else {
/* Default Behavior: If anything other than an OK message
is received, Send another OFF message. */
log.debug("Message is not OK message. Message received was: {}", l);
sendOffMessage();
}
}
switch (getFeedbackMode()) {
case EXACT:
handleExactModeFeedback(l);
break;
case MONITORING:
handleMonitoringModeFeedback(l);
break;
case DIRECT:
default:
// Default is direct mode
handleDirectModeFeedback(l);
}
}
/**
* Listen for the messages to the LI100/LI101.
*/
@Override
public void message(XNetMessage l) {
}
/**
* Handle a timeout notification.
*/
@Override
public synchronized void notifyTimeout(XNetMessage msg) {
log.debug("Notified of timeout on message {}", msg);
// If we're in the OFFSENT state, we need to send another OFF message.
if (internalState == OFFSENT) {
sendOffMessage();
}
}
/**
* With Direct Mode feedback, if we see ANY valid response to our
* request, we ask the command station to stop sending information
* to the stationary decoder.
* <p>
* No effort is made to interpret feedback when using direct mode.
*
* @param l an {@link XNetReply} message
*/
private synchronized void handleDirectModeFeedback(XNetReply l) {
/* If commanded state does not equal known state, we are
going to check to see if one of the following conditions
applies:
1) The received message is a feedback message for a turnout
and one of the two addresses to which it applies is our
address
2) We receive an "OK" message, indicating the command was
successfully sent
If either of these two cases occur, we trigger an off message
*/
log.debug("Handle Message for turnout {} in DIRECT feedback mode ", mNumber);
if (internalState == STATUSREQUESTSENT && l.isUnsolicited()) {
// set the reply as being solicited
l.resetUnsolicited();
}
if (getCommandedState() != getKnownState() || internalState == COMMANDSENT) {
if (l.isFeedbackBroadcastMessage()) {
int numDataBytes = l.getElement(0) & 0x0f;
for (int i = 1; i < numDataBytes; i += 2) {
int messageType = l.getFeedbackMessageType(i);
if (messageType == 0 || messageType == 1) {
if ((mNumber % 2 != 0
&& (l.getTurnoutMsgAddr(i) == mNumber))
|| (((mNumber % 2) == 0)
&& (l.getTurnoutMsgAddr(i) == mNumber - 1))) {
// This message includes feedback for this turnout
log.debug("Turnout {} DIRECT feedback mode - directed reply received.", mNumber);
// set the reply as being solicited
if (l.isUnsolicited()) {
l.resetUnsolicited();
}
sendOffMessage();
// Explicitly send two off messages in Direct Mode
sendOffMessage();
break;
}
}
}
} else if (l.isOkMessage()) {
// Finally, we may just receive an OK message.
log.debug("Turnout {} DIRECT feedback mode - OK message triggering OFF message.", mNumber);
sendOffMessage();
// Explicitly send two off messages in Direct Mode
sendOffMessage();
}
}
}
/**
* With Monitoring Mode feedback, if we see a feedback message, we
* interpret that message and use it to display our feedback.
* <p>
* After we send a request to operate a turnout, We ask the command
* station to stop sending information to the stationary decoder
* when the either a feedback message or an "OK" message is received.
*
* @param l an {@link XNetReply} message
*/
private synchronized void handleMonitoringModeFeedback(XNetReply l) {
/* In Monitoring Mode, We have two cases to check if CommandedState
does not equal KnownState, otherwise, we only want to check to
see if the messages we receive indicate this turnout chagned
state
*/
log.debug("Handle Message for turnout {} in MONITORING feedback mode ", mNumber);
if (internalState == IDLE || internalState == STATUSREQUESTSENT) {
if (l.isFeedbackBroadcastMessage()) {
// This is a feedback message, we need to check and see if it
// indicates this turnout is to change state or if it is for
// another turnout.
int numDataBytes = l.getElement(0) & 0x0f;
for (int i = 1; i < numDataBytes; i += 2) {
if (parseFeedbackMessage(l, i) != -1) {
log.debug("Turnout {} MONITORING feedback mode - state change from feedback.", mNumber);
break;
}
}
}
} else if (getCommandedState() != getKnownState()
|| internalState == COMMANDSENT) {
if (l.isFeedbackBroadcastMessage()) {
int numDataBytes = l.getElement(0) & 0x0f;
for (int i = 1; i < numDataBytes; i += 2) {
int messageType = l.getFeedbackMessageType(i);
if (messageType == 0 || messageType == 1) {
// In Monitoring mode, treat both turnouts with feedback
// and turnouts without feedback as turnouts without
// feedback. i.e. just interpret the feedback
// message, don't check to see if the motion is complete
if (parseFeedbackMessage(l, i) != -1) {
// We need to tell the turnout to shut off the output.
log.debug("Turnout {} MONITORING feedback mode - state change from feedback, CommandedState != KnownState.", mNumber);
sendOffMessage();
break;
}
}
}
} else if (l.isOkMessage()) {
// Finally, we may just receive an OK message.
log.debug("Turnout {} MONITORING feedback mode - OK message triggering OFF message.", mNumber);
sendOffMessage();
}
}
}
/**
* With Exact Mode feedback, if we see a feedback message, we
* interpret that message and use it to display our feedback.
* <p>
* After we send a request to operate a turnout, We ask the command
* station to stop sending information to the stationary decoder
* when the either a feedback message or an "OK" message is received.
*
* @param reply The reply message to process
*/
private synchronized void handleExactModeFeedback(XNetReply reply) {
// We have three cases to check if CommandedState does
// not equal KnownState, otherwise, we only want to check to
// see if the messages we receive indicate this turnout chagned
// state
log.debug("Handle Message for turnout {} in EXACT feedback mode ", mNumber);
if (getCommandedState() == getKnownState()
&& (internalState == IDLE || internalState == STATUSREQUESTSENT)) {
if (reply.isFeedbackBroadcastMessage()) {
// This is a feedback message, we need to check and see if it
// indicates this turnout is to change state or if it is for
// another turnout.
int numDataBytes = reply.getElement(0) & 0x0f;
for (int i = 1; i < numDataBytes; i += 2) {
if (parseFeedbackMessage(reply, i) != -1) {
log.debug("Turnout {} EXACT feedback mode - state change from feedback.", mNumber);
}
}
}
} else if (getCommandedState() != getKnownState()
|| internalState == COMMANDSENT
|| internalState == STATUSREQUESTSENT) {
if (reply.isFeedbackBroadcastMessage()) {
int numDataBytes = reply.getElement(0) & 0x0f;
for (int i = 1; i < numDataBytes; i += 2) {
if ((mNumber % 2 != 0
&& (reply.getTurnoutMsgAddr(i) == mNumber))
|| (((mNumber % 2) == 0)
&& (reply.getTurnoutMsgAddr(i) == mNumber - 1))) {
// This message includes feedback for this turnout
int messageType = reply.getFeedbackMessageType(i);
if (messageType == 1) {
// The first case is that we receive a message for
// this turnout and this turnout provides feedback.
// In this case, we want to check to see if the
// turnout has completed its movement before doing
// anything else.
if (!motionComplete(reply, i)) {
log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion not complete", mNumber);
// If the motion is NOT complete, send a feedback
// request for this nibble
XNetMessage msg = XNetMessage.getFeedbackRequestMsg(
mNumber, ((mNumber % 4) <= 1));
queueMessage(msg,STATUSREQUESTSENT ,null); //status is returned via the manager.
} else {
log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - motion complete", mNumber);
// If the motion is completed, behave as though
// this is a turnout without feedback.
parseFeedbackMessage(reply, i);
// We need to tell the turnout to shut off the
// output.
sendOffMessage();
}
} else if (messageType == 0) {
log.debug("Turnout {} EXACT feedback mode - state change from feedback, CommandedState!=KnownState - Turnout does not provide feedback", mNumber);
// The second case is that we receive a message about
// this turnout, and this turnout does not provide
// feedback. In this case, we want to check the
// contents of the message and act accordingly.
parseFeedbackMessage(reply, i);
// We need to tell the turnout to shut off the output.
sendOffMessage();
}
break;
}
}
} else if (reply.isOkMessage()) {
// Finally, we may just receive an OK message.
log.debug("Turnout {} EXACT feedback mode - OK message triggering OFF message.", mNumber);
sendOffMessage();
}
}
}
/**
* Send an "Off" message to the decoder for this output.
*/
protected synchronized void sendOffMessage() {
// We need to tell the turnout to shut off the output.
if (log.isDebugEnabled()) {
log.debug("Sending off message for turnout {} commanded state={}", mNumber, getCommandedState());
log.debug("Current Thread ID: {} Thread Name {}", java.lang.Thread.currentThread().getId(), java.lang.Thread.currentThread().getName());
}
XNetMessage msg = getOffMessage();
// Set the known state to the commanded state.
synchronized (this) {
// To avoid some of the command station busy
// messages, add a short delay before sending the
// first off message.
if (internalState != OFFSENT) {
jmri.util.ThreadingUtil.runOnLayoutDelayed( () ->
tc.sendHighPriorityXNetMessage(msg, this), 30);
newKnownState(getCommandedState());
internalState = OFFSENT;
return;
}
newKnownState(getCommandedState());
internalState = OFFSENT;
}
// Then send the message.
tc.sendHighPriorityXNetMessage(msg, this);
}
protected synchronized XNetMessage getOffMessage(){
return ( XNetMessage.getTurnoutCommandMsg(mNumber,
getCommandedState() == _mClosed,
getCommandedState() == _mThrown,
false) );
}
/**
* Parse the feedback message, and set the status of the turnout
* accordingly.
*
* @param l feedback broadcast message
* @param startByte first Byte of message to check
*
* @return 0 if address matches our turnout -1 otherwise
*/
private synchronized int parseFeedbackMessage(XNetReply l, int startByte) {
// check validity & addressing
// if this is an ODD numbered turnout, then we always get the
// right response from .getTurnoutMsgAddr. If this is an even
// numbered turnout, we need to check the messages for the odd
// numbered turnout in the nibble as well.
if (mNumber % 2 != 0 && (l.getTurnoutMsgAddr(startByte) == mNumber)) {
// is for this object, parse the message
log.debug("Message for turnout {}", mNumber);
if (internalState != IDLE && l.isUnsolicited()) {
l.resetUnsolicited();
}
if (l.getTurnoutStatus(startByte, 1) == THROWN) {
synchronized (this) {
newKnownState(_mThrown);
}
return (0);
} else if (l.getTurnoutStatus(startByte, 1) == CLOSED) {
synchronized (this) {
newKnownState(_mClosed);
}
return (0);
} else {
// the state is unknown or inconsistent. If the command state
// does not equal the known state, and the command repeat the
// last command
if (getCommandedState() != getKnownState()) {
forwardCommandChangeToLayout(getCommandedState());
} else {
sendQueuedMessage();
}
return -1;
}
} else if (((mNumber % 2) == 0)
&& (l.getTurnoutMsgAddr(startByte) == mNumber - 1)) {
// is for this object, parse message type
log.debug("Message for turnout {}", mNumber);
if (internalState != IDLE && l.isUnsolicited()) {
l.resetUnsolicited();
}
if (l.getTurnoutStatus(startByte, 0) == THROWN) {
synchronized (this) {
newKnownState(_mThrown);
}
return (0);
} else if (l.getTurnoutStatus(startByte, 0) == CLOSED) {
synchronized (this) {
newKnownState(_mClosed);
}
return (0);
} else {
// the state is unknown or inconsistent. If the command state
// does not equal the known state, and the command repeat the
// last command
if (getCommandedState() != getKnownState()) {
forwardCommandChangeToLayout(getCommandedState());
} else {
sendQueuedMessage();
}
return -1;
}
}
return (-1);
}
/**
* Determine if this feedback message says the turnout has completed
* its motion or not. Returns true for mostion complete, false
* otherwise.
*
* @param l feedback broadcast message
* @param startByte first Byte of message to check
*
* @return true if motion complete, false otherwise
*/
private synchronized boolean motionComplete(XNetReply l, int startByte) {
// check validity & addressing
// if this is an ODD numbered turnout, then we always get the
// right response from .getTurnoutMsgAddr. If this is an even
// numbered turnout, we need to check the messages for the odd
// numbered turnout in the nibble as well.
if ((mNumber % 2 != 0 && (l.getTurnoutMsgAddr(startByte) == mNumber)) ||
((mNumber % 2) == 0)
&& (l.getTurnoutMsgAddr(startByte) == mNumber - 1)) {
// is for this object, parse the message
return l.isFeedbackMotionComplete(startByte);
}
return (false);
}
@Override
public void dispose() {
this.removePropertyChangeListener(_stateListener);
super.dispose();
}
/**
* Internal class to use for listening to state changes.
*/
private static class XNetTurnoutStateListener implements java.beans.PropertyChangeListener {
XNetTurnout _turnout = null;
XNetTurnoutStateListener(XNetTurnout turnout) {
_turnout = turnout;
}
/**
* If we're not using DIRECT feedback mode, we need to listen for
* state changes to know when to send an OFF message after we set the
* known state.
* If we're using DIRECT mode, all of this is handled from the
* XpressNet Messages.
*/
@Override
public void propertyChange(java.beans.PropertyChangeEvent event) {
log.debug("propertyChange called");
// If we're using DIRECT feedback mode, we don't care what we see here
if (_turnout.getFeedbackMode() != DIRECT) {
if (log.isDebugEnabled()) {
log.debug("propertyChange Not Direct Mode property: {} old value {} new value {}", event.getPropertyName(), event.getOldValue(), event.getNewValue());
}
if (event.getPropertyName().equals("KnownState")) {
// Check to see if this is a change in the status
// triggered by a device on the layout, or a change in
// status we triggered.
int oldKnownState = ((Integer) event.getOldValue()).intValue();
int curKnownState = ((Integer) event.getNewValue()).intValue();
log.debug("propertyChange KnownState - old value {} new value {}", oldKnownState, curKnownState);
if (curKnownState != INCONSISTENT
&& _turnout.getCommandedState() == oldKnownState) {
// This was triggered by feedback on the layout, change
// the commanded state to reflect the new Known State
if (log.isDebugEnabled()) {
log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState());
}
_turnout.newCommandedState(curKnownState);
} else {
// Since we always set the KnownState to
// INCONSISTENT when we send a command, If the old
// known state is INCONSISTENT, we just want to send
// an off message
if (oldKnownState == INCONSISTENT) {
if (log.isDebugEnabled()) {
log.debug("propertyChange CommandedState: {}", _turnout.getCommandedState());
}
_turnout.sendOffMessage();
}
}
}
}
}
}
// data members
protected int mNumber; // XpressNet turnout number
XNetTurnoutStateListener _stateListener; // Internal class object
// A queue to hold outstanding messages
protected LinkedBlockingQueue<RequestMessage> requestList = null;
/**
* Send message from queue.
*/
protected synchronized void sendQueuedMessage() {
RequestMessage msg = null;
// check to see if the queue has a message in it, and if it does,
// remove the first message
if (!requestList.isEmpty()) {
log.debug("sending message to traffic controller");
// if the queue is not empty, remove the first message
// from the queue, send the message, and set the state machine
// to the requried state.
try {
msg = requestList.take();
} catch (java.lang.InterruptedException ie) {
return; // if there was an error, exit.
}
if (msg != null) {
internalState = msg.getState();
tc.sendXNetMessage(msg.getMsg(), msg.getListener());
}
} else {
log.debug("message queue empty");
// if the queue is empty, set the state to idle.
internalState = IDLE;
}
}
/**
* Queue a message.
*/
protected synchronized void queueMessage(XNetMessage m, int s, XNetListener l) {
log.debug("adding message {} to message queue. Current Internal State {}",m,internalState);
// put the message in the queue
RequestMessage msg = new RequestMessage(m, s, l);
try {
requestList.put(msg);
} catch (java.lang.InterruptedException ie) {
log.trace("Interrupted while queueing message {}",msg);
}
// if the state is idle, trigger the message send
if (internalState == IDLE ) {
sendQueuedMessage();
}
}
/**
* Internal class to hold a request message, along with the associated throttle state.
*/
protected static class RequestMessage {
private int state;
private XNetMessage msg;
private XNetListener listener;
RequestMessage(XNetMessage m, int s, XNetListener listener) {
state = s;
msg = m;
this.listener = listener;
}
int getState() {
return state;
}
XNetMessage getMsg() {
return msg;
}
XNetListener getListener() {
return listener;
}
}
private static final Logger log = LoggerFactory.getLogger(XNetTurnout.class);
}