001package jmri.jmrit.logixng.actions; 002 003import java.util.Locale; 004import java.util.Map; 005 006import jmri.*; 007import jmri.jmrit.logixng.*; 008import jmri.util.ThreadingUtil; 009 010/** 011 * Runs an engine. 012 * This action reads an analog expression with the loco address and sets its 013 * speed according to an alaog expression and the direction according to a 014 * digital expression. 015 * 016 * @author Daniel Bergqvist Copyright 2019 017 */ 018public final class ActionThrottle extends AbstractDigitalAction 019 implements FemaleSocketListener { 020 021 public static final int LOCO_ADDRESS_SOCKET = 0; 022 public static final int LOCO_SPEED_SOCKET = LOCO_ADDRESS_SOCKET + 1; 023 public static final int LOCO_DIRECTION_SOCKET = LOCO_SPEED_SOCKET + 1; 024 public static final int LOCO_FUNCTION_SOCKET = LOCO_DIRECTION_SOCKET + 1; 025 public static final int LOCO_FUNCTION_ONOFF_SOCKET = LOCO_FUNCTION_SOCKET + 1; 026 public static final int NUM_LOCO_SOCKETS = LOCO_FUNCTION_ONOFF_SOCKET + 1; 027 028 private SystemConnectionMemo _memo; 029 private ThrottleManager _throttleManager; 030 private ThrottleManager _oldThrottleManager; 031 032 // The throttle if we have one or if a request is sent, null otherwise 033 private DccThrottle _throttle; 034 private ThrottleListener _throttleListener; 035 036 private String _locoAddressSocketSystemName; 037 private String _locoSpeedSocketSystemName; 038 private String _locoDirectionSocketSystemName; 039 private String _locoFunctionSocketSystemName; 040 private String _locoFunctionOnOffSocketSystemName; 041 private final FemaleAnalogExpressionSocket _locoAddressSocket; 042 private final FemaleAnalogExpressionSocket _locoSpeedSocket; 043 private final FemaleDigitalExpressionSocket _locoDirectionSocket; 044 private final FemaleAnalogExpressionSocket _locoFunctionSocket; 045 private final FemaleDigitalExpressionSocket _locoFunctionOnOffSocket; 046 private boolean _stopLocoWhenSwitchingLoco = true; 047 private boolean _waitForThrottle = false; 048 049 050 public ActionThrottle(String sys, String user) { 051 super(sys, user); 052 _locoAddressSocket = InstanceManager.getDefault(AnalogExpressionManager.class) 053 .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_Address")); 054 _locoSpeedSocket = InstanceManager.getDefault(AnalogExpressionManager.class) 055 .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_Speed")); 056 _locoDirectionSocket = InstanceManager.getDefault(DigitalExpressionManager.class) 057 .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_Direction")); 058 _locoFunctionSocket = InstanceManager.getDefault(AnalogExpressionManager.class) 059 .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_Function")); 060 _locoFunctionOnOffSocket = InstanceManager.getDefault(DigitalExpressionManager.class) 061 .createFemaleSocket(this, this, Bundle.getMessage("ActionThrottle_SocketName_FunctionOnOff")); 062 063 // Set the _throttleManager variable 064 setMemo(null); 065 } 066 067 @Override 068 public Base getDeepCopy(Map<String, String> systemNames, Map<String, String> userNames) throws JmriException { 069 DigitalActionManager manager = InstanceManager.getDefault(DigitalActionManager.class); 070 String sysName = systemNames.get(getSystemName()); 071 String userName = userNames.get(getSystemName()); 072 if (sysName == null) sysName = manager.getAutoSystemName(); 073 ActionThrottle copy = new ActionThrottle(sysName, userName); 074 copy.setComment(getComment()); 075 copy.setStopLocoWhenSwitchingLoco(_stopLocoWhenSwitchingLoco); 076 copy.setWaitForThrottle(_waitForThrottle); 077 copy.setMemo(_memo); 078 return manager.registerAction(copy).deepCopyChildren(this, systemNames, userNames); 079 } 080 081 public void setMemo(SystemConnectionMemo memo) { 082 assertListenersAreNotRegistered(log, "setMemo"); 083 _memo = memo; 084 if (_memo != null) { 085 _throttleManager = _memo.get(jmri.ThrottleManager.class); 086 if (_throttleManager == null) { 087 throw new IllegalArgumentException("Memo "+memo.getUserName()+" doesn't have a ThrottleManager"); 088 } 089 } else { 090 _throttleManager = InstanceManager.getDefault(ThrottleManager.class); 091 } 092 } 093 094 public SystemConnectionMemo getMemo() { 095 return _memo; 096 } 097 098 /** {@inheritDoc} */ 099 @Override 100 public LogixNG_Category getCategory() { 101 return LogixNG_Category.ITEM; 102 } 103 104 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("WA_NOT_IN_LOOP") 105 /** {@inheritDoc} */ 106 @Override 107 public void execute() throws JmriException { 108 109 final Object lock = new Object(); 110 Reference<Boolean> throttleAquired = new Reference<>(false); 111 112 int currentLocoAddress = -1; 113 int newLocoAddress = -1; 114 115 if (_throttle != null) { 116 currentLocoAddress = _throttle.getLocoAddress().getNumber(); 117 } 118 119 if (_locoAddressSocket.isConnected()) { 120 newLocoAddress = 121 (int) ((MaleAnalogExpressionSocket)_locoAddressSocket.getConnectedSocket()) 122 .evaluate(); 123 } 124 125 if (_throttleManager != _oldThrottleManager) { 126 currentLocoAddress = -1; // Force request of new throttle 127 _oldThrottleManager = _throttleManager; 128 } 129 130 if (newLocoAddress != currentLocoAddress) { 131 132 if (_throttle != null) { 133 if (_stopLocoWhenSwitchingLoco) { 134 // Stop the loco 135 _throttle.setSpeedSetting(0); 136 } 137 // Release the loco 138 ThreadingUtil.runOnGUI(() -> { 139 _throttleManager.releaseThrottle(_throttle, _throttleListener); }); 140 _throttle = null; 141 } 142 143 if (newLocoAddress != -1) { 144 145 _throttleListener = new ThrottleListener() { 146 @Override 147 public void notifyThrottleFound(DccThrottle t) { 148 _throttle = t; 149 if (_waitForThrottle) { 150 synchronized(lock) { 151 throttleAquired.set(true); 152 lock.notifyAll(); 153 } 154 } else { 155 executeConditionalNG(); 156 } 157 } 158 159 @Override 160 public void notifyFailedThrottleRequest(LocoAddress address, String reason) { 161 log.warn("loco {} cannot be aquired", address.getNumber()); 162 } 163 164 @Override 165 public void notifyDecisionRequired(LocoAddress address, ThrottleListener.DecisionType question) { 166 log.warn("Loco {} cannot be aquired. Decision required.", address.getNumber()); 167 } 168 }; 169 170 final int locoAddr = newLocoAddress; 171 boolean result = ThreadingUtil.runOnGUIwithReturn( 172 () -> { return _throttleManager.requestThrottle(locoAddr, _throttleListener); }); 173 174 if (!result) { 175 log.warn("loco {} cannot be aquired", newLocoAddress); 176 } 177 178 if (_waitForThrottle) { 179 synchronized(lock) { 180 try { 181 if (! throttleAquired.get()) { 182 lock.wait(); 183 } 184 } catch (InterruptedException e) { 185 log.warn("Action Throttle was interrupted during wait for throttle to be aquired"); 186 } 187 } 188 } 189 } 190 191 } 192 193 // We have a throttle if _throttle is not null 194 if (_throttle != null) { 195 196 double speed = 0; 197 boolean isForward = true; 198 int function = 0; 199 boolean isFunctionOn = true; 200 201 if (_locoSpeedSocket.isConnected()) { 202 speed = 203 ((MaleAnalogExpressionSocket)_locoSpeedSocket.getConnectedSocket()) 204 .evaluate(); 205 } 206 207 if (_locoDirectionSocket.isConnected()) { 208 isForward = 209 ((MaleDigitalExpressionSocket)_locoDirectionSocket.getConnectedSocket()) 210 .evaluate(); 211 } 212 213 if (_locoFunctionSocket.isConnected()) { 214 function = (int) Math.round( 215 ((MaleAnalogExpressionSocket)_locoFunctionSocket.getConnectedSocket()) 216 .evaluate()); 217 } 218 219 if (_locoFunctionOnOffSocket.isConnected()) { 220 isFunctionOn = 221 ((MaleDigitalExpressionSocket)_locoFunctionOnOffSocket.getConnectedSocket()) 222 .evaluate(); 223 } 224 225 DccThrottle throttle = _throttle; 226 float spd = (float) speed; 227 boolean fwd = isForward; 228 int func = function; 229 boolean funcState = isFunctionOn; 230 jmri.util.ThreadingUtil.runOnLayoutWithJmriException(() -> { 231 if (_locoSpeedSocket.isConnected()) throttle.setSpeedSetting(spd); 232 if (_locoDirectionSocket.isConnected()) throttle.setIsForward(fwd); 233 if (_locoFunctionSocket.isConnected() && _locoFunctionOnOffSocket.isConnected()) { 234 throttle.setFunction(func, funcState); 235 } 236 }); 237 238 if (_waitForThrottle) { 239 ThreadingUtil.runOnGUI(() -> { 240 _throttleManager.releaseThrottle(_throttle, _throttleListener); }); 241 _throttle = null; 242 } 243 } 244 } 245 246 @Override 247 public FemaleSocket getChild(int index) throws IllegalArgumentException, UnsupportedOperationException { 248 switch (index) { 249 case LOCO_ADDRESS_SOCKET: 250 return _locoAddressSocket; 251 252 case LOCO_SPEED_SOCKET: 253 return _locoSpeedSocket; 254 255 case LOCO_DIRECTION_SOCKET: 256 return _locoDirectionSocket; 257 258 case LOCO_FUNCTION_SOCKET: 259 return _locoFunctionSocket; 260 261 case LOCO_FUNCTION_ONOFF_SOCKET: 262 return _locoFunctionOnOffSocket; 263 264 default: 265 throw new IllegalArgumentException( 266 String.format("index has invalid value: %d", index)); 267 } 268 } 269 270 @Override 271 public int getChildCount() { 272 return NUM_LOCO_SOCKETS; 273 } 274 275 @Override 276 public void connected(FemaleSocket socket) { 277 if (socket == _locoAddressSocket) { 278 _locoAddressSocketSystemName = socket.getConnectedSocket().getSystemName(); 279 executeConditionalNG(); 280 } else if (socket == _locoSpeedSocket) { 281 _locoSpeedSocketSystemName = socket.getConnectedSocket().getSystemName(); 282 executeConditionalNG(); 283 } else if (socket == _locoDirectionSocket) { 284 _locoDirectionSocketSystemName = socket.getConnectedSocket().getSystemName(); 285 executeConditionalNG(); 286 } else if (socket == _locoFunctionSocket) { 287 _locoFunctionSocketSystemName = socket.getConnectedSocket().getSystemName(); 288 executeConditionalNG(); 289 } else if (socket == _locoFunctionOnOffSocket) { 290 _locoFunctionOnOffSocketSystemName = socket.getConnectedSocket().getSystemName(); 291 executeConditionalNG(); 292 } else { 293 throw new IllegalArgumentException("unkown socket"); 294 } 295 } 296 297 @Override 298 public void disconnected(FemaleSocket socket) { 299 if (socket == _locoAddressSocket) { 300 if (_throttle != null) { 301 // Stop the loco 302 _throttle.setSpeedSetting(0); 303 // Release the loco 304 ThreadingUtil.runOnGUI(() -> { 305 _throttleManager.releaseThrottle(_throttle, _throttleListener); }); 306 } 307 _locoAddressSocketSystemName = null; 308 executeConditionalNG(); 309 } else if (socket == _locoSpeedSocket) { 310 _locoSpeedSocketSystemName = null; 311 executeConditionalNG(); 312 } else if (socket == _locoDirectionSocket) { 313 _locoDirectionSocketSystemName = null; 314 executeConditionalNG(); 315 } else if (socket == _locoFunctionSocket) { 316 _locoFunctionSocketSystemName = null; 317 executeConditionalNG(); 318 } else if (socket == _locoFunctionOnOffSocket) { 319 _locoFunctionOnOffSocketSystemName = null; 320 executeConditionalNG(); 321 } else { 322 throw new IllegalArgumentException("unkown socket"); 323 } 324 } 325 326 private void executeConditionalNG() { 327 if (_listenersAreRegistered) { 328 ConditionalNG c = getConditionalNG(); 329 if (c != null) { 330 c.execute(); 331 } 332 } 333 } 334 335 @Override 336 public String getShortDescription(Locale locale) { 337 return Bundle.getMessage(locale, "ActionThrottle_Short"); 338 } 339 340 @Override 341 public String getLongDescription(Locale locale) { 342 String stopLocoWhenSwitchingLocoStr = _stopLocoWhenSwitchingLoco 343 ? Bundle.getMessage("ActionThrottle_StopLocoWhenSwitchingLoco") 344 : Bundle.getMessage("ActionThrottle_DontStopLocoWhenSwitchingLoco"); 345 String waitForThrottleStr = _waitForThrottle 346 ? Bundle.getMessage("ActionThrottle_WaitForThrottle") 347 : Bundle.getMessage("ActionThrottle_DontWaitForThrottle"); 348 349 if (_memo != null) { 350 if (_waitForThrottle) { 351 return Bundle.getMessage(locale, "ActionThrottle_LongConnection1", 352 _memo.getUserName(), waitForThrottleStr); 353 } else { 354 return Bundle.getMessage(locale, "ActionThrottle_LongConnection2", 355 _memo.getUserName(), waitForThrottleStr, stopLocoWhenSwitchingLocoStr); 356 } 357 } else { 358 if (_waitForThrottle) { 359 return Bundle.getMessage(locale, "ActionThrottle_Long1", 360 waitForThrottleStr); 361 } else { 362 return Bundle.getMessage(locale, "ActionThrottle_Long2", 363 waitForThrottleStr, stopLocoWhenSwitchingLocoStr); 364 } 365 } 366 } 367 368 public FemaleAnalogExpressionSocket getLocoAddressSocket() { 369 return _locoAddressSocket; 370 } 371 372 public String getLocoAddressSocketSystemName() { 373 return _locoAddressSocketSystemName; 374 } 375 376 public void setLocoAddressSocketSystemName(String systemName) { 377 _locoAddressSocketSystemName = systemName; 378 } 379 380 public FemaleAnalogExpressionSocket getLocoSpeedSocket() { 381 return _locoSpeedSocket; 382 } 383 384 public String getLocoSpeedSocketSystemName() { 385 return _locoSpeedSocketSystemName; 386 } 387 388 public void setLocoSpeedSocketSystemName(String systemName) { 389 _locoSpeedSocketSystemName = systemName; 390 } 391 392 public FemaleDigitalExpressionSocket getLocoDirectionSocket() { 393 return _locoDirectionSocket; 394 } 395 396 public String getLocoDirectionSocketSystemName() { 397 return _locoDirectionSocketSystemName; 398 } 399 400 public void setLocoDirectionSocketSystemName(String systemName) { 401 _locoDirectionSocketSystemName = systemName; 402 } 403 404 public FemaleAnalogExpressionSocket getLocoFunctionSocket() { 405 return _locoFunctionSocket; 406 } 407 408 public String getLocoFunctionSocketSystemName() { 409 return _locoFunctionSocketSystemName; 410 } 411 412 public void setLocoFunctionSocketSystemName(String systemName) { 413 _locoFunctionSocketSystemName = systemName; 414 } 415 416 public FemaleDigitalExpressionSocket getLocoFunctionOnOffSocket() { 417 return _locoFunctionOnOffSocket; 418 } 419 420 public String getLocoFunctionOnOffSocketSystemName() { 421 return _locoFunctionOnOffSocketSystemName; 422 } 423 424 public void setLocoFunctionOnOffSocketSystemName(String systemName) { 425 _locoFunctionOnOffSocketSystemName = systemName; 426 } 427 428 /** {@inheritDoc} */ 429 @Override 430 public void setup() { 431 try { 432 if ( !_locoAddressSocket.isConnected() 433 || !_locoAddressSocket.getConnectedSocket().getSystemName() 434 .equals(_locoAddressSocketSystemName)) { 435 436 String socketSystemName = _locoAddressSocketSystemName; 437 _locoAddressSocket.disconnect(); 438 if (socketSystemName != null) { 439 MaleSocket maleSocket = 440 InstanceManager.getDefault(AnalogExpressionManager.class) 441 .getBySystemName(socketSystemName); 442 _locoAddressSocket.disconnect(); 443 if (maleSocket != null) { 444 _locoAddressSocket.connect(maleSocket); 445 maleSocket.setup(); 446 } else { 447 log.error("cannot load analog expression {}", socketSystemName); 448 } 449 } 450 } else { 451 _locoAddressSocket.getConnectedSocket().setup(); 452 } 453 454 if ( !_locoSpeedSocket.isConnected() 455 || !_locoSpeedSocket.getConnectedSocket().getSystemName() 456 .equals(_locoSpeedSocketSystemName)) { 457 458 String socketSystemName = _locoSpeedSocketSystemName; 459 _locoSpeedSocket.disconnect(); 460 if (socketSystemName != null) { 461 MaleSocket maleSocket = 462 InstanceManager.getDefault(AnalogExpressionManager.class) 463 .getBySystemName(socketSystemName); 464 _locoSpeedSocket.disconnect(); 465 if (maleSocket != null) { 466 _locoSpeedSocket.connect(maleSocket); 467 maleSocket.setup(); 468 } else { 469 log.error("cannot load analog expression {}", socketSystemName); 470 } 471 } 472 } else { 473 _locoSpeedSocket.getConnectedSocket().setup(); 474 } 475 476 if ( !_locoDirectionSocket.isConnected() 477 || !_locoDirectionSocket.getConnectedSocket().getSystemName() 478 .equals(_locoDirectionSocketSystemName)) { 479 480 String socketSystemName = _locoDirectionSocketSystemName; 481 _locoDirectionSocket.disconnect(); 482 if (socketSystemName != null) { 483 MaleSocket maleSocket = 484 InstanceManager.getDefault(DigitalExpressionManager.class) 485 .getBySystemName(socketSystemName); 486 _locoDirectionSocket.disconnect(); 487 if (maleSocket != null) { 488 _locoDirectionSocket.connect(maleSocket); 489 maleSocket.setup(); 490 } else { 491 log.error("cannot load digital expression {}", socketSystemName); 492 } 493 } 494 } else { 495 _locoDirectionSocket.getConnectedSocket().setup(); 496 } 497 498 if ( !_locoFunctionSocket.isConnected() 499 || !_locoFunctionSocket.getConnectedSocket().getSystemName() 500 .equals(_locoFunctionSocketSystemName)) { 501 502 String socketSystemName = _locoFunctionSocketSystemName; 503 _locoFunctionSocket.disconnect(); 504 if (socketSystemName != null) { 505 MaleSocket maleSocket = 506 InstanceManager.getDefault(AnalogExpressionManager.class) 507 .getBySystemName(socketSystemName); 508 _locoFunctionSocket.disconnect(); 509 if (maleSocket != null) { 510 _locoFunctionSocket.connect(maleSocket); 511 maleSocket.setup(); 512 } else { 513 log.error("cannot load analog expression {}", socketSystemName); 514 } 515 } 516 } else { 517 _locoFunctionSocket.getConnectedSocket().setup(); 518 } 519 520 if ( !_locoFunctionOnOffSocket.isConnected() 521 || !_locoFunctionOnOffSocket.getConnectedSocket().getSystemName() 522 .equals(_locoFunctionOnOffSocketSystemName)) { 523 524 String socketSystemName = _locoFunctionOnOffSocketSystemName; 525 _locoFunctionOnOffSocket.disconnect(); 526 if (socketSystemName != null) { 527 MaleSocket maleSocket = 528 InstanceManager.getDefault(DigitalExpressionManager.class) 529 .getBySystemName(socketSystemName); 530 _locoFunctionOnOffSocket.disconnect(); 531 if (maleSocket != null) { 532 _locoFunctionOnOffSocket.connect(maleSocket); 533 maleSocket.setup(); 534 } else { 535 log.error("cannot load digital expression {}", socketSystemName); 536 } 537 } 538 } else { 539 _locoFunctionOnOffSocket.getConnectedSocket().setup(); 540 } 541 } catch (SocketAlreadyConnectedException ex) { 542 // This shouldn't happen and is a runtime error if it does. 543 throw new RuntimeException("socket is already connected"); 544 } 545 } 546 547 public boolean isStopLocoWhenSwitchingLoco() { 548 return _stopLocoWhenSwitchingLoco; 549 } 550 551 public void setStopLocoWhenSwitchingLoco(boolean value) { 552 _stopLocoWhenSwitchingLoco = value; 553 } 554 555 public boolean isWaitForThrottle() { 556 return _waitForThrottle; 557 } 558 559 public void setWaitForThrottle(boolean value) { 560 _waitForThrottle = value; 561 } 562 563 /** {@inheritDoc} */ 564 @Override 565 public void registerListenersForThisClass() { 566 _listenersAreRegistered = true; 567 } 568 569 /** {@inheritDoc} */ 570 @Override 571 public void unregisterListenersForThisClass() { 572 _listenersAreRegistered = false; 573 } 574 575 /** {@inheritDoc} */ 576 @Override 577 public void disposeMe() { 578 if (_throttle != null) { 579 ThreadingUtil.runOnGUI(() -> { 580 _throttleManager.releaseThrottle(_throttle, _throttleListener); }); 581 } 582 } 583 584 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ActionThrottle.class); 585 586}