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("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("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("feedbackchange", this); 129 turnout.addPropertyChangeListener("turnoutFeedbackFirstSensorChange", this); 130 turnout.addPropertyChangeListener("turnoutFeedbackSecondSensorChange", this); 131 if (hasTurnoutFeedback(turnout)) { 132 turnout.addPropertyChangeListener("CommandedState", _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("feedbackchange", this); 141 turnout.removePropertyChangeListener("turnoutFeedbackFirstSensorChange", this); 142 turnout.removePropertyChangeListener("turnoutFeedbackSecondSensorChange", this); 143 if (ti != null && ti._hasListener) { 144 turnout.removePropertyChangeListener("CommandedState", _turnoutListener); 145 ti._hasListener = false; 146 } 147 } 148 149 /** {@inheritDoc} */ 150 @Override 151 public synchronized void propertyChange(PropertyChangeEvent evt) { 152 if (evt.getPropertyName().equals("beans")) { 153 if (!(evt.getSource() instanceof TurnoutManager)) return; 154 TurnoutManager manager = (TurnoutManager)evt.getSource(); 155 if (evt.getNewValue() != null) { 156 String sysName = evt.getNewValue().toString(); 157 Turnout turnout = manager.getBySystemName(sysName); 158 if (_listenersAreRegistered && (turnout != null)) { 159 addTurnoutListener(turnout); 160 } 161 } else if (evt.getOldValue() != null) { 162 String sysName = evt.getOldValue().toString(); 163 TurnoutInfo turnoutInfo = _turnouts.get(sysName); 164 if (_listenersAreRegistered 165 && (turnoutInfo != null) 166 && (turnoutInfo._turnout != null)) { 167 removeTurnoutListener(turnoutInfo._turnout); 168 } 169 } 170 } 171 172 if (evt.getPropertyName().equals("feedbackchange") 173 || evt.getPropertyName().equals("turnoutFeedbackFirstSensorChange") 174 || evt.getPropertyName().equals("turnoutFeedbackSecondSensorChange")) { 175 176 TurnoutInfo ti = _turnouts.get(evt.getSource().toString()); 177 if (hasTurnoutFeedback(ti._turnout)) { 178 if (!ti._hasListener) { 179 ti._turnout.addPropertyChangeListener("CommandedState", _turnoutListener); 180 ti._hasListener = true; 181 } 182 } else { 183 if (ti._hasListener) { 184 ti._turnout.removePropertyChangeListener("CommandedState", _turnoutListener); 185 ti._hasListener = false; 186 } 187 } 188 } 189 } 190 191 @Override 192 public void vetoableChange(java.beans.PropertyChangeEvent evt) throws java.beans.PropertyVetoException { 193 if ("DoDelete".equals(evt.getPropertyName())) { // No I18N 194 if (evt.getOldValue() instanceof Turnout) { 195 removeTurnoutListener((Turnout) evt.getOldValue()); 196 } 197 } 198 } 199 200 /** {@inheritDoc} */ 201 @Override 202 public void disposeMe() { 203 } 204 205 206 private static class TurnoutInfo { 207 208 private final Turnout _turnout; 209 private boolean _hasListener; 210 211 TurnoutInfo(Turnout turnout) { 212 _turnout = turnout; 213 } 214 } 215 216 217 private class TurnoutListener implements PropertyChangeListener { 218 219 private void manageTurnout(Turnout t, int newState) { 220 synchronized (_timerTasks) { 221 if (_timerTasks.containsKey(t)) { 222 TurnoutTimerTask task = _timerTasks.get(t); 223 task.cancel(); 224 _timerTasks.remove(t); 225 } 226 TurnoutTimerTask task = new TurnoutTimerTask(t, newState); 227 _timerTasks.put(t, task); 228 TimerUtil.schedule(task, _delay * 1000L); 229 } 230 } 231 232 @Override 233 public void propertyChange(PropertyChangeEvent evt) { 234// System.out.format("Source: %s, name: %s, old: %s, new: %s%n", evt.getSource(), evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()); 235 236 if (evt.getPropertyName().equals("CommandedState")) { 237 String sysName = evt.getSource().toString(); 238 TurnoutManager tm = InstanceManager.getDefault(TurnoutManager.class); 239 Turnout t = tm.getBySystemName(sysName); 240 if (t == null) { 241 log.error("Turnout {} does not exists in manager {}", sysName, tm); 242 return; 243 } 244 245 switch (t.getFeedbackMode()) { 246 case Turnout.DIRECT: 247 case Turnout.SIGNAL: 248 case Turnout.DELAYED: 249 // Do nothing 250 break; 251 252 case Turnout.EXACT: 253 case Turnout.INDIRECT: 254 case Turnout.MONITORING: 255 case Turnout.ONESENSOR: 256 case Turnout.TWOSENSOR: 257 case Turnout.LNALTERNATE: 258 // Hardware feedback 259 manageTurnout(t, (int) evt.getNewValue()); 260 break; 261 262 default: 263 log.debug("Unsupported turnout feedback mode: {}, {}", t.getFeedbackMode(), t.getFeedbackModeName()); 264 } 265 } 266 } 267 268 } 269 270 private class TurnoutTimerTask extends java.util.TimerTask { 271 272 private final Turnout _turnout; 273 private final int _newState; 274 275 private TurnoutTimerTask(Turnout t, int newState) { 276 _turnout = t; 277 _newState = newState; 278 startMove(); 279 } 280 281 private void startMove() { 282 Sensor sensor1; 283 Sensor sensor2; 284 285 switch (_turnout.getFeedbackMode()) { 286 case Turnout.EXACT: 287 // Hardware feedback 288 break; 289 290 case Turnout.INDIRECT: 291 // Hardware feedback 292 break; 293 294 case Turnout.MONITORING: 295 // Hardware feedback 296 break; 297 298 case Turnout.ONESENSOR: 299 // Do nothing 300 break; 301 302 case Turnout.TWOSENSOR: 303 sensor1 = _turnout.getFirstSensor(); 304 if (sensor1 != null) sensor1.setCommandedState(Sensor.INACTIVE); 305 sensor2 = _turnout.getSecondSensor(); 306 if (sensor2 != null) sensor2.setCommandedState(Sensor.INACTIVE); 307 break; 308 309 case Turnout.LNALTERNATE: 310 // Hardware feedback 311 break; 312 313 default: 314 log.debug("Unsupported turnout feedback mode: {}, {}", _turnout.getFeedbackMode(), _turnout.getFeedbackModeName()); 315 } 316 } 317 318 private void stopMove() { 319 Sensor sensor; 320 321 switch (_turnout.getFeedbackMode()) { 322 case Turnout.EXACT: 323 if (_turnout instanceof jmri.jmrix.loconet.LnTurnout) { 324 jmri.jmrix.loconet.LnTurnout lnTurnout = (jmri.jmrix.loconet.LnTurnout)_turnout; 325 lnTurnout.newKnownState(_newState); 326 } else if (_turnout instanceof jmri.jmrix.lenz.XNetTurnout) { 327 jmri.jmrix.lenz.XNetTurnout xnetTurnout = (jmri.jmrix.lenz.XNetTurnout)_turnout; 328 xnetTurnout.newKnownState(_newState); 329 } else { 330 log.warn("Unknown type of turnout {}, {}", _turnout.getSystemName(), _turnout.getDisplayName()); 331 } 332 break; 333 334 case Turnout.INDIRECT: 335 // Hardware feedback 336 break; 337 338 case Turnout.MONITORING: 339 // Hardware feedback 340 break; 341 342 case Turnout.ONESENSOR: 343 sensor = _turnout.getFirstSensor(); 344 if (_newState == Turnout.CLOSED) { 345 if (sensor != null) sensor.setCommandedState(Sensor.INACTIVE); 346 } else if (_newState == Turnout.THROWN) { 347 if (sensor != null) sensor.setCommandedState(Sensor.ACTIVE); 348 } 349 break; 350 351 case Turnout.TWOSENSOR: 352 if (_newState == Turnout.CLOSED) { 353 sensor = _turnout.getSecondSensor(); 354 if (sensor != null) sensor.setCommandedState(Sensor.ACTIVE); 355 } else if (_newState == Turnout.THROWN) { 356 sensor = _turnout.getFirstSensor(); 357 if (sensor != null) sensor.setCommandedState(Sensor.ACTIVE); 358 } 359 break; 360 361 case Turnout.LNALTERNATE: 362 // Hardware feedback 363 break; 364 365 default: 366 log.debug("Unsupported turnout feedback mode: {}, {}", _turnout.getFeedbackMode(), _turnout.getFeedbackModeName()); 367 } 368 } 369 370 @Override 371 public void run() { 372 synchronized (_timerTasks) { 373 _timerTasks.remove(_turnout); 374 } 375 stopMove(); 376 } 377 } 378 379 380 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SimulateTurnoutFeedback.class); 381}