001package jmri.jmrix.tams; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.Turnout; 005import jmri.implementation.AbstractTurnout; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * Implement a Turnout via Tams communications. 011 * <p> 012 * This object doesn't listen to the Tams communications. This is because it 013 * should be the only object that is sending messages for this turnout; more 014 * than one Turnout object pointing to a single device is not allowed. 015 * <p> 016 * Based on work by Bob Jacobsen and Kevin Dickerson Copyright 017 * 018 * @author Jan Boen 019 */ 020public class TamsTurnout extends AbstractTurnout 021 implements TamsListener { 022 023 /** 024 * Tams turnouts use the NMRA number (0-2040) as their numerical 025 * identification in the system name. 026 * 027 * @param number DCC address of the turnout 028 * @param prefix system prefix 029 * @param etc Tams system connection traffic controller 030 */ 031 public TamsTurnout(int number, String prefix, TamsTrafficController etc) { 032 super(prefix + "T" + number); 033 _number = number; 034 tc = etc; 035 // Request status of turnout 036 TamsMessage m = new TamsMessage("xT " + _number + ",,0"); 037 m.setBinary(false); 038 m.setReplyType('T'); 039 tc.sendTamsMessage(m, this); 040 041 _validFeedbackTypes |= MONITORING; 042 _activeFeedbackType = MONITORING; 043 044 // if needed, create the list of feedback mode 045 // names with additional LocoNet-specific modes 046 if (modeNames == null) { 047 initFeedbackModes(); 048 } 049 _validFeedbackNames = modeNames; 050 _validFeedbackModes = modeValues; 051 } 052 053 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", 054 justification = "Only used during creation of 1st turnout") 055 private void initFeedbackModes() { 056 log.debug("*** initFeedbackModes ***"); 057 if (_validFeedbackNames.length != _validFeedbackModes.length) { 058 log.error("int and string feedback arrays different length"); 059 } 060 String[] tempModeNames = new String[_validFeedbackNames.length + 1]; 061 int[] tempModeValues = new int[_validFeedbackNames.length + 1]; 062 for (int i = 0; i < _validFeedbackNames.length; i++) { 063 tempModeNames[i] = _validFeedbackNames[i]; 064 tempModeValues[i] = _validFeedbackModes[i]; 065 } 066 tempModeNames[_validFeedbackNames.length] = "MONITORING"; 067 tempModeValues[_validFeedbackNames.length] = MONITORING; 068 069 modeNames = tempModeNames; 070 modeValues = tempModeValues; 071 } 072 073 static String[] modeNames = null; 074 static int[] modeValues = null; 075 076 private final TamsTrafficController tc; 077 078 /** 079 * {@inheritDoc} 080 */ 081 @Override 082 protected void forwardCommandChangeToLayout(int newState) { 083 log.debug("*** forwardCommandChangeToLayout ***"); 084 // sort out states 085 if ((newState & Turnout.CLOSED) != 0) { 086 // first look for the double case, which we can't handle 087 if ((newState & Turnout.THROWN) != 0) { 088 // this is the disaster case! 089 log.error("Cannot command both CLOSED and THROWN {}", newState); 090 } else { 091 // send a CLOSED command 092 sendMessage(!getInverted()); 093 } 094 } else { 095 // send a THROWN command 096 sendMessage(getInverted()); 097 } 098 } 099 100 // data members 101 int _number; // turnout number 102 103 /** 104 * Set the turnout known state to reflect what's been observed from the 105 * command station messages. A change there means that somebody commanded a 106 * state change (by using a throttle), and that command has 107 * already taken effect. Hence we use "newCommandedState" to indicate it's 108 * taken place. Must be followed by "newKnownState" to complete the turnout 109 * action. 110 * 111 * @param state Observed state, updated state from command station 112 */ 113 synchronized void setCommandedStateFromCS(int state) { 114 log.debug("*** setCommandedStateFromCS ***"); 115 if ((getFeedbackMode() != MONITORING)) { 116 log.debug("Returning"); 117 return; 118 } 119 log.debug("Setting to state {}", state); 120 newCommandedState(state); 121 } 122 123 /** 124 * Set the turnout known state to reflect what's been observed from the 125 * command station messages. A change there means that somebody commanded a 126 * state change (by using a throttle), and that command has 127 * already taken effect. Hence we use "newKnownState" to indicate it's taken 128 * place. 129 * 130 * @param state Observed state, updated state from command station 131 */ 132 synchronized void setKnownStateFromCS(int state) { 133 log.debug("*** setKnownStateFromCS ***"); 134 if ((getFeedbackMode() != MONITORING)) { 135 return; 136 } 137 newKnownState(state); 138 } 139 140 @Override 141 public void turnoutPushbuttonLockout(boolean b) { 142 } 143 144 /** 145 * Tams turnouts can be inverted. 146 */ 147 @Override 148 public boolean canInvert() { 149 return true; 150 } 151 152 /** 153 * Tell the layout to go to new state. 154 * 155 * @param closed State of the turnout to be sent to the command station 156 */ 157 protected void sendMessage(boolean closed) { 158 log.debug("*** sendMessage ***"); 159 // get control 160 TamsMessage m = new TamsMessage("xT " + _number + "," + (closed ? "r" : "g") + ",1"); 161 tc.sendTamsMessage(m, this); 162 } 163 164 // Listen for status changes from Tams system. 165 @Override 166 public void reply(TamsReply m) { 167 log.debug("*** TamsReply ***"); 168 log.debug("m.match(\"T\") = {}", Integer.toString(m.match("T"))); 169 String msg = m.toString(); 170 log.debug("Turnout Reply = {}", msg); 171 if (m.match("T") == 0) { 172 String[] lines = msg.split(" "); 173 if (lines[1].equals("" + _number)) { 174 //updateReceived = true; // uncomment when pollForStatus() works 175 if (lines[2].equals("r") || lines[2].equals("0")) { 176 log.debug("Turnout {} = CLOSED", _number); 177 setCommandedStateFromCS(Turnout.CLOSED); 178 setKnownStateFromCS(Turnout.CLOSED); 179 } else { 180 log.debug("Turnout {} = THROWN", _number); 181 setCommandedStateFromCS(Turnout.THROWN); 182 setKnownStateFromCS(Turnout.THROWN); 183 } 184 } 185 } 186 } 187 188 //boolean updateReceived = false; 189 190 /*protected void pollForStatus() { 191 if (_activeFeedbackType == MONITORING) { 192 log.debug("*** pollForStatus ***"); 193 //if we received an update last time we send a request again, but if we did not we shall skip it once and try again next time. 194 if (updateReceived) { 195 updateReceived = false; 196 TamsMessage m = new TamsMessage("xT " + _number + ",,1"); 197 m.setTimeout(TamsMessage.POLLTIMEOUT); 198 tc.sendTamsMessage(m, this); 199 } else { 200 updateReceived = true; 201 } 202 } 203 }*/ 204 205 @Override 206 public void setFeedbackMode(int mode) throws IllegalArgumentException { 207 log.debug("*** setFeedbackMode ***"); 208 TamsMessage m = new TamsMessage("xT " + _number + ",,1"); 209 if (mode == MONITORING) { 210 tc.sendTamsMessage(m, this);//Only send a message once 211 //The rest gets done via polling from TamsTurnoutManager 212 } 213 super.setFeedbackMode(mode); 214 } 215 216 @Override 217 public void message(TamsMessage m) { 218 log.debug("*** message ***"); 219 // messages are ignored 220 } 221 222 @Override 223 public void dispose() { 224 log.debug("*** dispose ***"); 225 TamsMessage m = new TamsMessage("xT " + _number + ",,1"); 226 tc.removePollMessage(m, this); 227 super.dispose(); 228 } 229 230 private final static Logger log = LoggerFactory.getLogger(TamsTurnout.class); 231 232}