001package jmri.jmrix.bidib; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import jmri.NamedBean; 006import jmri.Turnout; 007import jmri.implementation.AbstractTurnout; 008import org.bidib.jbidibc.messages.enums.LcOutputType; 009import org.bidib.jbidibc.simulation.comm.SimulationBidib; 010 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * BiDiB implementation of the Turnout interface. 016 * 017 * @author Bob Jacobsen Copyright (C) 2001 018 * @author Eckart Meyer Copyright (C) 2019-2023 019 */ 020public class BiDiBTurnout extends AbstractTurnout implements BiDiBNamedBeanInterface { 021 022 // data members 023 BiDiBAddress addr; 024 private final char typeLetter; 025 026 static String[] modeNames = null; 027 static int[] modeValues = null; 028 029 private BiDiBTrafficController tc = null; 030 //MessageListener messageListener = null; 031 private BiDiBOutputMessageHandler messageHandler = null; 032 033 /** 034 * Create a turnout. 035 * 036 * @param systemName to be created 037 * @param mgr Turnout Manager, we get the memo object and the type letter (T) from the manager 038 */ 039// @SuppressWarnings("OverridableMethodCallInConstructor") 040 @SuppressFBWarnings(value = "ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",justification = "Write safe by design") 041 public BiDiBTurnout(String systemName, BiDiBTurnoutManager mgr) { 042 super(systemName); 043 tc = mgr.getMemo().getBiDiBTrafficController(); 044 addr = new BiDiBAddress(systemName, mgr.typeLetter(), mgr.getMemo()); 045 log.info("New TURNOUT created: {} -> {}", systemName, addr); 046 typeLetter = mgr.typeLetter(); 047 048 // new mode list 049 if (_validFeedbackNames.length != _validFeedbackModes.length) { 050 log.error("int and string feedback arrays different length"); 051 } 052 modeNames = new String[_validFeedbackNames.length + 1]; 053 modeValues = new int[_validFeedbackNames.length + 1]; 054 for (int i = 0; i < _validFeedbackNames.length; i++) { 055 modeNames[i] = _validFeedbackNames[i]; 056 modeValues[i] = _validFeedbackModes[i]; 057 } 058 modeNames[_validFeedbackNames.length] = "MONITORING"; 059 modeValues[_validFeedbackNames.length] = MONITORING; 060 _validFeedbackTypes |= MONITORING; 061 _validFeedbackNames = modeNames; 062 _validFeedbackModes = modeValues; 063 064 _activeFeedbackType = MONITORING; //default for new Turnouts 065 066 createTurnoutListener(); 067 068 messageHandler.sendQueryConfig(); 069 } 070 071 @Override 072 public BiDiBAddress getAddr() { 073 return addr; 074 } 075 076 /** 077 * {@inheritDoc} 078 */ 079 @Override 080 public void finishLoad() { 081 sendQuery(); 082 } 083 084 /** 085 * {@inheritDoc} 086 */ 087 @Override 088 public void nodeNew() { 089 //create a new BiDiBAddress 090 addr = new BiDiBAddress(getSystemName(), typeLetter, tc.getSystemConnectionMemo()); 091 if (addr.isValid()) { 092 log.info("new turnout address created: {} -> {}", getSystemName(), addr); 093 messageHandler.sendQueryConfig(); 094 messageHandler.waitQueryConfig(); 095 log.debug("current known state is {}, commanded state is {}", getKnownState(), getCommandedState()); 096// if (getKnownState() == NamedBean.UNKNOWN || getKnownState() == NamedBean.INCONSISTENT) { 097// log.debug("state is unknown, so query from node"); 098// sendQuery(); 099// } 100// else { 101// log.debug("state is known, so set node"); 102// forwardCommandChangeToLayout(getKnownState()); 103// } 104 forwardCommandChangeToLayout(getCommandedState()); 105 } 106 } 107 108 /** 109 * {@inheritDoc} 110 */ 111 @Override 112 public void nodeLost() { 113 newKnownState(NamedBean.UNKNOWN); 114 } 115 116 /** 117 * {@inheritDoc} 118 */ 119 @Override 120 public boolean canInvert() { 121 // Turnouts do support inversion 122 //log.trace("canInvert"); 123 return true; 124 } 125 126 /** 127 * {@inheritDoc} 128 */ 129 @Override 130 protected void forwardCommandChangeToLayout(int s) { 131 if ((tc.getBidib() instanceof SimulationBidib) && addr.getAddrString().equals("Xfb7600C602:a0")) { // **** TEST hack only on for simulation 132 tc.TEST((s & Turnout.THROWN) != 0);/////DEBUG 133 } 134 // Handle a request to change state 135 // sort out states 136 log.trace("forwardCommandChangeToLayout: {}, addr: {}", s, addr); 137 if ((s & Turnout.UNKNOWN) != 0) { 138 // what to do here? 139 } 140 else { 141 if ((s & Turnout.CLOSED) != 0) { 142 // first look for the double case, which we can't handle 143 if ((s & Turnout.THROWN) != 0) { 144 // this is the disaster case! 145 log.error("Cannot command both CLOSED and THROWN {}", s); 146 } else { 147 // send a CLOSED command 148 sendMessage(true ^ getInverted()); 149 } 150 } else { 151 // send a THROWN command 152 sendMessage(false ^ getInverted()); 153 } 154 } 155 } 156 157 /** 158 * {@inheritDoc} 159 */ 160 @Override 161 public void requestUpdateFromLayout() { 162 log.trace("requestUpdateFromLayout"); 163 if (_activeFeedbackType == MONITORING) { 164 sendQuery(); 165 } 166 super.requestUpdateFromLayout(); //query sensors for ONESENSOR and TWOSENSOR turnouts 167 } 168 169 @Override 170 protected void turnoutPushbuttonLockout(boolean _pushButtonLockout) { 171 // unsupported in BiDiB, but must be implemented 172 } 173 174 /** 175 * Request the state of the accessory from the layout. 176 * The listener gets the answer. 177 */ 178 public void sendQuery() { 179 messageHandler.sendQuery(); 180 } 181 182 /** 183 * Send a thrown/closed message to BiDiB 184 * 185 * @param closed true of the turnout should be switched to CLOSED, false to switch to THROWN 186 */ 187 protected void sendMessage(boolean closed) { 188 // TODO: check FEATURE_GEN_SWITCH_ACK 189 int state = closed ? 0 : 1; 190 if (addr.isPortAddr()) { 191 switch (messageHandler.getLcType()) { 192 case LIGHTPORT: 193 state = closed ? 2 : 3; //use Dim function - we can't configure this so far... 194 break; 195 case SERVOPORT: 196 case ANALOGPORT: 197 case BACKLIGHTPORT: 198 state = closed ? 0 : 255; 199 break; 200 case MOTORPORT: 201 state = closed ? 0 : 126; 202 break; 203 case INPUTPORT: 204 log.warn("output to INPUT port is not possible, addr: {}", addr); 205 return; 206 default: 207 // just drop through 208 break; 209 } 210 } 211 if (getFeedbackMode() == MONITORING) { 212 newKnownState(INCONSISTENT); 213 } 214 messageHandler.sendOutput(state); 215 } 216 217 218 private void createTurnoutListener() { 219 //messageHandler = new BiDiBOutputMessageHandler("TURNOUT", addr, tc) { 220 messageHandler = new BiDiBOutputMessageHandler(this, "TURNOUT", tc) { 221 @Override 222 public void newOutputState(int state) { 223 224 int newState = (state == 0) ? CLOSED : THROWN; 225 226 if (addr.isPortAddr() && getLcType() == LcOutputType.LIGHTPORT) { 227 if (state == 2) { 228 newState = CLOSED; //BIDIB_PORT_DIMM_OFF - does not make much sense though... 229 } 230 } 231 if (getInverted()) { 232 newState = (newState == THROWN) ? CLOSED : THROWN; 233 } 234 log.debug("TURNOUT new state: {} addr: {}", newState, addr); 235 newKnownState(newState); 236 } 237 @Override 238 public void outputWait(int time) { 239 log.debug("TURNOUT wait: {} addr: {}", time, addr); 240 //newKnownState(getCommandedState()); 241 } 242 @Override 243 public void errorState(int err) { 244 log.warn("TURNOUT error: {} addr: {}", err, addr); 245 newKnownState(INCONSISTENT); 246 } 247 }; 248 tc.addMessageListener(messageHandler); 249 } 250 251 /** 252 * {@inheritDoc} 253 * 254 * Remove the Message Listener for this turnout 255 */ 256 @Override 257 public void dispose() { 258 if (messageHandler != null) { 259 tc.removeMessageListener(messageHandler); 260 messageHandler = null; 261 } 262 super.dispose(); 263 } 264 265 266 private final static Logger log = LoggerFactory.getLogger(BiDiBTurnout.class); 267 268}