001package jmri.jmrit.logixng.actions; 002 003import java.beans.*; 004import java.util.*; 005 006import jmri.*; 007import jmri.jmrit.logixng.*; 008import jmri.util.TimerUtil; 009 010/** 011 * Simulates turnout feedback. 012 * @author Daniel Bergqvist (C) 2022 013 */ 014public class SimulateTurnoutFeedback extends AbstractDigitalAction 015 implements PropertyChangeListener, VetoableChangeListener { 016 017 private final TurnoutListener _turnoutListener = new TurnoutListener(); 018 private final Map<Turnout, TurnoutTimerTask> _timerTasks = new HashMap<>(); 019 020 private int _delay = 3; // Delay in seconds 021 private final Map<String, TurnoutInfo> _turnouts = new HashMap<>(); 022 private boolean _hasBeenExecuted = false; 023 024 025 public SimulateTurnoutFeedback(String sys, String user) 026 throws BadUserNameException, BadSystemNameException { 027 super(sys, user, Category.OTHER); 028 } 029 030 @Override 031 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 032 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 033 String sysName = systemNames.get(getSystemName()); 034 String userName = userNames.get(getSystemName()); 035 if (sysName == null) sysName = manager.getAutoSystemName(); 036 SimulateTurnoutFeedback copy = new SimulateTurnoutFeedback(sysName, userName); 037 copy.setComment(getComment()); 038 copy._delay = _delay; 039 return manager.registerAction(copy); 040 } 041 042 @Override 043 public String getShortDescription(Locale locale) { 044 return Bundle.getMessage(locale, "SimulateTurnoutFeedback_Short"); 045 } 046 047 @Override 048 public String getLongDescription(Locale locale) { 049 return Bundle.getMessage(locale, "SimulateTurnoutFeedback_Long", _delay); 050 } 051 052 @Override 053 public void setup() { 054 // Do nothing 055 } 056 057 @Override 058 public void execute() throws JmriException { 059 if (!_hasBeenExecuted && _listenersAreRegistered) { 060 registerTurnoutListeners(); 061 } 062 _hasBeenExecuted = true; 063 } 064 065 private void registerTurnoutListeners() { 066 TurnoutManager tm = InstanceManager.getDefault(TurnoutManager.class); 067 for (Turnout t : tm.getNamedBeanSet()) { 068 addTurnoutListener(t); 069 } 070 tm.addPropertyChangeListener(Manager.PROPERTY_BEANS, this); 071 tm.addVetoableChangeListener(this); 072 } 073 074 /** {@inheritDoc} */ 075 @Override 076 public synchronized void registerListenersForThisClass() { 077 if (!_listenersAreRegistered) { 078 _listenersAreRegistered = true; 079 if (_hasBeenExecuted) { 080 registerTurnoutListeners(); 081 } 082 } 083 } 084 085 /** {@inheritDoc} */ 086 @Override 087 public synchronized void unregisterListenersForThisClass() { 088 if (_listenersAreRegistered) { 089 TurnoutManager tm = InstanceManager.getDefault(TurnoutManager.class); 090 for (Turnout t : tm.getNamedBeanSet()) { 091 removeTurnoutListener(t); 092 } 093 tm.removePropertyChangeListener(Manager.PROPERTY_BEANS, this); 094 tm.removeVetoableChangeListener(this); 095 _listenersAreRegistered = false; 096 } 097 } 098 099 private boolean hasTurnoutFeedback(Turnout t) { 100 switch (t.getFeedbackMode()) { 101 case Turnout.DIRECT: 102 case Turnout.SIGNAL: 103 case Turnout.DELAYED: 104 return false; 105 106 case Turnout.ONESENSOR: 107 return t.getFirstSensor() != null; 108 109 case Turnout.TWOSENSOR: 110 return t.getFirstSensor() != null && t.getSecondSensor() != null; 111 112 case Turnout.EXACT: 113 case Turnout.INDIRECT: 114 case Turnout.MONITORING: 115 case Turnout.LNALTERNATE: 116 return true; 117 118 default: 119 log.debug("Unsupported turnout feedback mode: {}, {}", t.getFeedbackMode(), t.getFeedbackModeName()); 120 return false; 121 } 122 } 123 124 private void addTurnoutListener(Turnout turnout) { 125 if (!_turnouts.containsKey(turnout.getSystemName())) { 126 TurnoutInfo ti = new TurnoutInfo(turnout); 127 _turnouts.put(turnout.getSystemName(), ti); 128 turnout.addPropertyChangeListener(Turnout.PROPERTY_FEEDBACK_MODE, this); 129 turnout.addPropertyChangeListener(Turnout.PROPERTY_TURNOUT_FEEDBACK_FIRST_SENSOR, this); 130 turnout.addPropertyChangeListener(Turnout.PROPERTY_TURNOUT_FEEDBACK_SECOND_SENSOR, this); 131 if (hasTurnoutFeedback(turnout)) { 132 turnout.addPropertyChangeListener(Turnout.PROPERTY_COMMANDED_STATE, _turnoutListener); 133 ti._hasListener = true; 134 } 135 } 136 } 137 138 private void removeTurnoutListener(Turnout turnout) { 139 TurnoutInfo ti = _turnouts.remove(turnout.getSystemName()); 140 turnout.removePropertyChangeListener(Turnout.PROPERTY_FEEDBACK_MODE, this); 141 turnout.removePropertyChangeListener(Turnout.PROPERTY_TURNOUT_FEEDBACK_FIRST_SENSOR, this); 142 turnout.removePropertyChangeListener(Turnout.PROPERTY_TURNOUT_FEEDBACK_SECOND_SENSOR, this); 143 if (ti != null && ti._hasListener) { 144 turnout.removePropertyChangeListener(Turnout.PROPERTY_COMMANDED_STATE, _turnoutListener); 145 ti._hasListener = false; 146 } 147 } 148 149 /** {@inheritDoc} */ 150 @Override 151 public synchronized void propertyChange(PropertyChangeEvent evt) { 152 if (Manager.PROPERTY_BEANS.equals(evt.getPropertyName())) { 153 if (!(evt.getSource() instanceof TurnoutManager)) { 154 log.error("Non-TurnoutManager sent event : {}", evt); 155 return; 156 } 157 TurnoutManager manager = (TurnoutManager)evt.getSource(); 158 if (evt.getNewValue() != null) { 159 String sysName = evt.getNewValue().toString(); 160 Turnout turnout = manager.getBySystemName(sysName); 161 if (_listenersAreRegistered && (turnout != null)) { 162 addTurnoutListener(turnout); 163 } 164 } else if (evt.getOldValue() != null) { 165 String sysName = evt.getOldValue().toString(); 166 TurnoutInfo turnoutInfo = _turnouts.get(sysName); 167 if (_listenersAreRegistered 168 && (turnoutInfo != null) 169 && (turnoutInfo._turnout != null)) { 170 removeTurnoutListener(turnoutInfo._turnout); 171 } 172 } 173 } 174 175 String evp = evt.getPropertyName(); 176 if ( Turnout.PROPERTY_FEEDBACK_MODE.equals(evp) 177 || Turnout.PROPERTY_TURNOUT_FEEDBACK_FIRST_SENSOR.equals(evp) 178 || Turnout.PROPERTY_TURNOUT_FEEDBACK_SECOND_SENSOR.equals(evp)) { 179 180 TurnoutInfo ti = _turnouts.get(evt.getSource().toString()); 181 if (hasTurnoutFeedback(ti._turnout)) { 182 if (!ti._hasListener) { 183 ti._turnout.addPropertyChangeListener(Turnout.PROPERTY_COMMANDED_STATE, _turnoutListener); 184 ti._hasListener = true; 185 } 186 } else { 187 if (ti._hasListener) { 188 ti._turnout.removePropertyChangeListener(Turnout.PROPERTY_COMMANDED_STATE, _turnoutListener); 189 ti._hasListener = false; 190 } 191 } 192 } 193 } 194 195 @Override 196 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 197 if ( Manager.PROPERTY_DO_DELETE.equals(evt.getPropertyName()) 198 && evt.getOldValue() instanceof Turnout ) { 199 removeTurnoutListener((Turnout) evt.getOldValue()); 200 } 201 } 202 203 /** {@inheritDoc} */ 204 @Override 205 public void disposeMe() { 206 } 207 208 209 private static class TurnoutInfo { 210 211 private final Turnout _turnout; 212 private boolean _hasListener; 213 214 TurnoutInfo(Turnout turnout) { 215 _turnout = turnout; 216 } 217 } 218 219 220 private class TurnoutListener implements PropertyChangeListener { 221 222 private void manageTurnout(Turnout t, int newState) { 223 synchronized (_timerTasks) { 224 if (_timerTasks.containsKey(t)) { 225 TurnoutTimerTask task = _timerTasks.get(t); 226 task.cancel(); 227 _timerTasks.remove(t); 228 } 229 TurnoutTimerTask task = new TurnoutTimerTask(t, newState); 230 _timerTasks.put(t, task); 231 TimerUtil.schedule(task, _delay * 1000L); 232 } 233 } 234 235 @Override 236 public void propertyChange(PropertyChangeEvent evt) { 237// System.out.format("Source: %s, name: %s, old: %s, new: %s%n", evt.getSource(), evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()); 238 239 if ( Turnout.PROPERTY_COMMANDED_STATE.equals(evt.getPropertyName())) { 240 String sysName = evt.getSource().toString(); 241 TurnoutManager tm = InstanceManager.getDefault(TurnoutManager.class); 242 Turnout t = tm.getBySystemName(sysName); 243 if (t == null) { 244 log.error("Turnout {} does not exists in manager {}", sysName, tm); 245 return; 246 } 247 248 switch (t.getFeedbackMode()) { 249 case Turnout.DIRECT: 250 case Turnout.SIGNAL: 251 case Turnout.DELAYED: 252 // Do nothing 253 break; 254 255 case Turnout.EXACT: 256 case Turnout.INDIRECT: 257 case Turnout.MONITORING: 258 case Turnout.ONESENSOR: 259 case Turnout.TWOSENSOR: 260 case Turnout.LNALTERNATE: 261 // Hardware feedback 262 manageTurnout(t, (int) evt.getNewValue()); 263 break; 264 265 default: 266 log.debug("Unsupported turnout feedback mode: {}, {}", t.getFeedbackMode(), t.getFeedbackModeName()); 267 } 268 } 269 } 270 271 } 272 273 private class TurnoutTimerTask extends java.util.TimerTask { 274 275 private final Turnout _turnout; 276 private final int _newState; 277 278 private TurnoutTimerTask(Turnout t, int newState) { 279 _turnout = t; 280 _newState = newState; 281 startMove(); 282 } 283 284 private void startMove() { 285 Sensor sensor1; 286 Sensor sensor2; 287 288 switch (_turnout.getFeedbackMode()) { 289 case Turnout.EXACT: 290 // Hardware feedback 291 break; 292 293 case Turnout.INDIRECT: 294 // Hardware feedback 295 break; 296 297 case Turnout.MONITORING: 298 // Hardware feedback 299 break; 300 301 case Turnout.ONESENSOR: 302 // Do nothing 303 break; 304 305 case Turnout.TWOSENSOR: 306 sensor1 = _turnout.getFirstSensor(); 307 if (sensor1 != null) sensor1.setCommandedState(Sensor.INACTIVE); 308 sensor2 = _turnout.getSecondSensor(); 309 if (sensor2 != null) sensor2.setCommandedState(Sensor.INACTIVE); 310 break; 311 312 case Turnout.LNALTERNATE: 313 // Hardware feedback 314 break; 315 316 default: 317 log.debug("Unsupported turnout feedback mode: {}, {}", _turnout.getFeedbackMode(), _turnout.getFeedbackModeName()); 318 } 319 } 320 321 private void stopMove() { 322 Sensor sensor; 323 324 switch (_turnout.getFeedbackMode()) { 325 case Turnout.EXACT: 326 if (_turnout instanceof jmri.jmrix.loconet.LnTurnout) { 327 jmri.jmrix.loconet.LnTurnout lnTurnout = (jmri.jmrix.loconet.LnTurnout)_turnout; 328 lnTurnout.newKnownState(_newState); 329 } else if (_turnout instanceof jmri.jmrix.lenz.XNetTurnout) { 330 jmri.jmrix.lenz.XNetTurnout xnetTurnout = (jmri.jmrix.lenz.XNetTurnout)_turnout; 331 xnetTurnout.newKnownState(_newState); 332 } else { 333 log.warn("Unknown type of turnout {}, {}", _turnout.getSystemName(), _turnout.getDisplayName()); 334 } 335 break; 336 337 case Turnout.INDIRECT: 338 // Hardware feedback 339 break; 340 341 case Turnout.MONITORING: 342 // Hardware feedback 343 break; 344 345 case Turnout.ONESENSOR: 346 sensor = _turnout.getFirstSensor(); 347 if (_newState == Turnout.CLOSED) { 348 if (sensor != null) sensor.setCommandedState(Sensor.INACTIVE); 349 } else if (_newState == Turnout.THROWN) { 350 if (sensor != null) sensor.setCommandedState(Sensor.ACTIVE); 351 } 352 break; 353 354 case Turnout.TWOSENSOR: 355 if (_newState == Turnout.CLOSED) { 356 sensor = _turnout.getSecondSensor(); 357 if (sensor != null) sensor.setCommandedState(Sensor.ACTIVE); 358 } else if (_newState == Turnout.THROWN) { 359 sensor = _turnout.getFirstSensor(); 360 if (sensor != null) sensor.setCommandedState(Sensor.ACTIVE); 361 } 362 break; 363 364 case Turnout.LNALTERNATE: 365 // Hardware feedback 366 break; 367 368 default: 369 log.debug("Unsupported turnout feedback mode: {}, {}", _turnout.getFeedbackMode(), _turnout.getFeedbackModeName()); 370 } 371 } 372 373 @Override 374 public void run() { 375 synchronized (_timerTasks) { 376 _timerTasks.remove(_turnout); 377 } 378 stopMove(); 379 } 380 } 381 382 383 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SimulateTurnoutFeedback.class); 384}