001package jmri.jmrix; 002 003import com.fasterxml.jackson.databind.util.StdDateFormat; 004import java.beans.PropertyChangeListener; 005import java.util.Arrays; 006import java.util.Date; 007 008import jmri.BasicRosterEntry; 009import jmri.CommandStation; 010import jmri.LocoAddress; 011import jmri.SpeedStepMode; 012import jmri.DccLocoAddress; 013import jmri.DccThrottle; 014import jmri.InstanceManager; 015import jmri.SystemConnectionMemo; 016import jmri.Throttle; 017import jmri.ThrottleListener; 018import jmri.beans.PropertyChangeSupport; 019 020import jmri.jmrit.roster.RosterEntry; 021 022import javax.annotation.Nonnull; 023import javax.annotation.concurrent.GuardedBy; 024 025/** 026 * An abstract implementation of DccThrottle. Based on Glen Oberhauser's 027 * original LnThrottleManager implementation. 028 * <p> 029 * Note that this implements DccThrottle, not Throttle directly, so it has some 030 * DCC-specific content. 031 * 032 * @author Bob Jacobsen Copyright (C) 2001, 2005 033 */ 034abstract public class AbstractThrottle extends PropertyChangeSupport implements DccThrottle { 035 036 @GuardedBy("this") 037 protected float speedSetting; 038 /** 039 * Question: should we set a default speed step mode so it's never zero? 040 */ 041 protected SpeedStepMode speedStepMode = SpeedStepMode.UNKNOWN; 042 protected boolean isForward; 043 044 /** 045 * Array of Function values. 046 * <p> 047 * Contains current Boolean value for functions. 048 * This array should not be accessed directly by Throttles, 049 * use setFunction / getFunction / updateFunction. 050 * Needs to be same length as FUNCTION_MOMENTARY_BOOLEAN_ARRAY. 051 */ 052 private final boolean[] FUNCTION_BOOLEAN_ARRAY; 053 054 /** 055 * Array of Momentary Function values. 056 * <p> 057 * Contains current Boolean value for Momentary function settings. 058 * Needs to be same length as FUNCTION_BOOLEAN_ARRAY. 059 */ 060 private final boolean[] FUNCTION_MOMENTARY_BOOLEAN_ARRAY; 061 062 /** 063 * Constants to represent Function Groups. 064 * <p> 065 * The are the same groupings for both normal Functions and Momentary. 066 */ 067 protected static final int[] FUNCTION_GROUPS = new int[]{ 068 1, 1, 1, 1, 1, /** 0-4 */ 069 2, 2, 2, 2, /** 5-8 */ 3, 3, 3, 3, /** 9-12 */ 070 4, 4, 4, 4, 4, 4, 4, 4, /** 13-20 */ 5, 5, 5, 5, 5, 5, 5, 5, /** 21-28 */ 071 6, 6, 6, 6, 6, 6, 6, 6, /** 29-36 */ 7, 7, 7, 7, 7, 7, 7, 7, /** 37-44 */ 072 8, 8, 8, 8, 8, 8, 8, 8, /** 45-52 */ 9, 9, 9, 9, 9, 9, 9, 9, /** 53-60 */ 073 10, 10, 10, 10, 10, 10, 10, 10, /** 61-68 */ 074 }; 075 076 /** 077 * Is this object still usable? Set false after dispose, this variable is 078 * used to check for incorrect usage. 079 */ 080 protected boolean active; 081 082 /** 083 * Create a new AbstractThrottle with Functions 0-28.. 084 * <p> 085 * All function and momentary functions set to Off. 086 * @param memo System Connection. 087 */ 088 public AbstractThrottle(SystemConnectionMemo memo) { 089 active = true; 090 adapterMemo = memo; 091 FUNCTION_BOOLEAN_ARRAY = new boolean[29]; 092 FUNCTION_MOMENTARY_BOOLEAN_ARRAY = new boolean[29]; 093 } 094 095 /** 096 * Create a new AbstractThrottle with custom number of functions. 097 * <p> 098 * All function and momentary functions set to Off. 099 * @param memo System Connection this throttle is on 100 * @param totalFunctions total number of functions available, including 0 101 */ 102 public AbstractThrottle(SystemConnectionMemo memo, int totalFunctions) { 103 active = true; 104 adapterMemo = memo; 105 FUNCTION_BOOLEAN_ARRAY = new boolean[totalFunctions]; 106 FUNCTION_MOMENTARY_BOOLEAN_ARRAY = new boolean[totalFunctions]; 107 } 108 109 /** 110 * System Connection this throttle is on 111 */ 112 protected SystemConnectionMemo adapterMemo; 113 114 /** 115 * speed - expressed as a value {@literal 0.0 -> 1.0.} Negative means 116 * emergency stop. This is a bound parameter. 117 * 118 * @return speed 119 */ 120 @Override 121 public synchronized float getSpeedSetting() { 122 return speedSetting; 123 } 124 125 /** 126 * setSpeedSetting - Implementing functions should override this function, 127 * but should either make a call to super.setSpeedSetting() to notify the 128 * listeners at the end of their work, or should notify the listeners 129 * themselves. 130 */ 131 @Override 132 public void setSpeedSetting(float speed) { 133 setSpeedSetting(speed, false, false); 134 record(speed); 135 } 136 137 /** 138 * setSpeedSetting - Implementations should override this method only if 139 * they normally suppress messages to the system if, as far as JMRI can 140 * tell, the new message would make no difference to the system state (eg. 141 * the speed is the same, or effectivly the same, as the existing speed). 142 * Then, the boolean options can affect this behaviour. 143 * 144 * @param speed the new speed 145 * @param allowDuplicates don't suppress messages 146 * @param allowDuplicatesOnStop don't suppress messages if the new speed is 147 * 'stop' 148 */ 149 @Override 150 public synchronized void setSpeedSetting(float speed, boolean allowDuplicates, boolean allowDuplicatesOnStop) { 151 if (Math.abs(this.speedSetting - speed) > 0.0001) { 152 firePropertyChange(SPEEDSETTING, this.speedSetting, this.speedSetting = speed); 153 } 154 record(speed); 155 } 156 157 /** 158 * setSpeedSettingAgain - set the speed and don't ever suppress the sending 159 * of messages to the system 160 * 161 * @param speed the new speed 162 */ 163 @Override 164 public void setSpeedSettingAgain(float speed) { 165 setSpeedSetting(speed, true, true); 166 } 167 168 /** 169 * direction This is an bound parameter. 170 * 171 * @return true if locomotive is running forward 172 */ 173 @Override 174 public boolean getIsForward() { 175 return isForward; 176 } 177 178 /** 179 * Implementing functions should override this function, but should either 180 * make a call to super.setIsForward() to notify the listeners, or should 181 * notify the listeners themselves. 182 * 183 * @param forward true if forward; false otherwise 184 */ 185 @Override 186 public void setIsForward(boolean forward) { 187 firePropertyChange(ISFORWARD, isForward, isForward = forward); 188 } 189 190 /* 191 * functions - note that we use the naming for DCC, though that's not the 192 * implication; see also DccThrottle interface 193 */ 194 195 /** 196 * {@inheritDoc} 197 */ 198 @Override 199 @Nonnull 200 public boolean[] getFunctions() { 201 return Arrays.copyOf(FUNCTION_BOOLEAN_ARRAY,FUNCTION_BOOLEAN_ARRAY.length); 202 } 203 204 /** 205 * {@inheritDoc} 206 */ 207 @Override 208 @Nonnull 209 public boolean[] getFunctionsMomentary() { 210 return Arrays.copyOf(FUNCTION_MOMENTARY_BOOLEAN_ARRAY, 211 FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length); 212 } 213 214 /** 215 * {@inheritDoc} 216 */ 217 @Override 218 public boolean getFunction(int fN) { 219 if (fN<0 || fN > FUNCTION_BOOLEAN_ARRAY.length-1){ 220 log.warn("Unhandled get function: {} {}", fN, this.getClass().getName()); 221 return false; 222 } 223 return FUNCTION_BOOLEAN_ARRAY[fN]; 224 } 225 226 /** 227 * Get Function Number without warning if Throttle does not support. 228 * When sending a whole Function Group, a function number may not be present. 229 * @param fN Function Number 230 * @return Function value, or false if not present. 231 */ 232 protected boolean getFunctionNoWarn(int fN) { 233 if (fN<0 || fN > FUNCTION_BOOLEAN_ARRAY.length-1){ 234 return false; 235 } 236 return FUNCTION_BOOLEAN_ARRAY[fN]; 237 } 238 239 /** 240 * {@inheritDoc} 241 */ 242 @Override 243 public boolean getFunctionMomentary(int fN) { 244 if (fN<0 || fN > FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length-1){ 245 log.warn("Unhandled get momentary function: {} {}", fN, this.getClass().getName()); 246 return false; 247 } 248 return FUNCTION_MOMENTARY_BOOLEAN_ARRAY[fN]; 249 250 } 251 252 /** 253 * Get Momentary Function Number without warning if Throttle does not support. 254 * When sending a whole Function Group, a function number may not be present. 255 * @param fN Function Number 256 * @return Function value, or false if not present. 257 */ 258 protected boolean getFunctionMomentaryNoWarn(int fN) { 259 if (fN<0 || fN > FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length-1){ 260 return false; 261 } 262 return FUNCTION_MOMENTARY_BOOLEAN_ARRAY[fN]; 263 } 264 265 /** 266 * Notify listeners that a Throttle has disconnected and is no longer 267 * available for use. 268 * <p> 269 * For when throttles have been stolen or encounter hardware error, and a 270 * normal release / dispose is not possible. 271 */ 272 protected void notifyThrottleDisconnect() { 273 firePropertyChange("ThrottleConnected", true, false); // NOI18N 274 } 275 276 // set initial values purely for changelistener following 277 // the 1st true or false will always get sent 278 private Boolean _dispatchEnabled = null; 279 private Boolean _releaseEnabled = null; 280 281 /** 282 * Notify listeners that a Throttle has Dispatch enabled or disabled. 283 * <p> 284 * For systems where dispatch availability is variable. 285 * <p> 286 * Does not notify if existing value is unchanged. 287 * 288 * @param newVal true if Dispatch enabled, else false 289 * 290 */ 291 @Override 292 public void notifyThrottleDispatchEnabled(boolean newVal) { 293 firePropertyChange("DispatchEnabled", _dispatchEnabled, _dispatchEnabled = newVal); // NOI18N 294 } 295 296 /** 297 * Notify listeners that a Throttle has Release enabled or disabled. 298 * <p> 299 * For systems where release availability is variable. 300 * <p> 301 * Does not notify if existing value is unchanged. 302 * 303 * @param newVal true if Release enabled, else false 304 * 305 */ 306 @Override 307 public void notifyThrottleReleaseEnabled(boolean newVal) { 308 firePropertyChange("ReleaseEnabled", _releaseEnabled, _releaseEnabled = newVal); // NOI18N 309 } 310 311 /** 312 * Temporary behaviour only allowing unique PCLs. 313 * To support Throttle PCL's ( eg. WiThrottle Server ) that rely on the 314 * previous behaviour of only allowing 1 unique PCL instance. 315 * To be removed when WiThrottle Server has been updated. 316 * {@inheritDoc} 317 */ 318 @Override 319 public void addPropertyChangeListener(PropertyChangeListener l) { 320 if (l == null) { 321 return; 322 } 323 log.debug("addPropertyChangeListener(): Adding property change {} to {}", l.getClass().getSimpleName(), getLocoAddress()); 324 if ( Arrays.asList(getPropertyChangeListeners()).contains(l) ){ 325 log.warn("Preventing {} adding duplicate PCL to {}", l.getClass().getSimpleName(), this.getClass().getName()); 326 return; 327 } 328 super.addPropertyChangeListener(l); 329 log.debug("addPropertyChangeListener(): throttle: {} listeners size is {}", getLocoAddress(), getPropertyChangeListeners().length); 330 } 331 332 /** 333 * {@inheritDoc} 334 */ 335 @Override 336 public void removePropertyChangeListener(PropertyChangeListener l) { 337 if (l == null) { 338 return; 339 } 340 log.debug("removePropertyChangeListener(): Removing property change {} from {}", l.getClass().getSimpleName(), getLocoAddress()); 341 super.removePropertyChangeListener(l); 342 log.debug("removePropertyChangeListener(): throttle: {} listeners size is {}", getLocoAddress(), getPropertyChangeListeners().length); 343 if (getPropertyChangeListeners().length == 0) { 344 log.debug("No listeners so calling ThrottleManager.dispose with an empty ThrottleListener for {}",getLocoAddress()); 345 InstanceManager.throttleManagerInstance().disposeThrottle(this, new ThrottleListener() { 346 @Override 347 public void notifyFailedThrottleRequest(LocoAddress address, String reason) { 348 } 349 350 @Override 351 public void notifyThrottleFound(DccThrottle t) { 352 } 353 354 @Override 355 public void notifyDecisionRequired(LocoAddress address, DecisionType question) { 356 } 357 }); 358 } 359 } 360 361 /** 362 * Call from a ThrottleListener to dispose of the throttle instance 363 * 364 * @param l the listener requesting the dispose 365 * 366 */ 367 @Override 368 public void dispose(ThrottleListener l) { 369 if (!active) { 370 log.error("Dispose called when not active {}", this.getClass().getName()); 371 } 372 InstanceManager.throttleManagerInstance().disposeThrottle(this, l); 373 } 374 375 /** 376 * {@inheritDoc} 377 */ 378 @Override 379 public void dispatch(ThrottleListener l) { 380 if (!active) { 381 log.warn("dispatch called when not active {}", this.getClass().getName()); 382 } 383 InstanceManager.throttleManagerInstance().dispatchThrottle(this, l); 384 } 385 386 /** 387 * {@inheritDoc} 388 */ 389 @Override 390 public void release(ThrottleListener l) { 391 if (!active) { 392 log.warn("release called when not active {}",this.getClass().getName()); 393 } 394 InstanceManager.throttleManagerInstance().releaseThrottle(this, l); 395 } 396 397 /** 398 * Dispose when finished with this Throttle. May be used in tests for cleanup. 399 * Throttles normally call {@link #finishRecord()} here. 400 */ 401 protected abstract void throttleDispose(); 402 403 /** 404 * Handle quantized speed. Note this can change! Value returned is 405 * always positive. 406 * 407 * @return 1 divided by the number of speed steps this DCC throttle supports 408 */ 409 @Override 410 public float getSpeedIncrement() { 411 return speedStepMode.increment; 412 } 413 414 /* 415 * functions - note that we use the naming for DCC, though that's not the 416 * implication; see also DccThrottle interface 417 */ 418 419 /** 420 * Send whole (DCC) Function Group for a particular function number. 421 * @param functionNum Function Number 422 * @param momentary False to send normal function status, true to send momentary. 423 */ 424 protected void sendFunctionGroup(int functionNum, boolean momentary){ 425 switch (FUNCTION_GROUPS[functionNum]) { 426 case 1: 427 if (momentary) sendMomentaryFunctionGroup1(); else sendFunctionGroup1(); 428 break; 429 case 2: 430 if (momentary) sendMomentaryFunctionGroup2(); else sendFunctionGroup2(); 431 break; 432 case 3: 433 if (momentary) sendMomentaryFunctionGroup3(); else sendFunctionGroup3(); 434 break; 435 case 4: 436 if (momentary) sendMomentaryFunctionGroup4(); else sendFunctionGroup4(); 437 break; 438 case 5: 439 if (momentary) sendMomentaryFunctionGroup5(); else sendFunctionGroup5(); 440 break; 441 case 6: 442 if (momentary) sendMomentaryFunctionGroup6(); else sendFunctionGroup6(); 443 break; 444 case 7: 445 if (momentary) sendMomentaryFunctionGroup7(); else sendFunctionGroup7(); 446 break; 447 case 8: 448 if (momentary) sendMomentaryFunctionGroup8(); else sendFunctionGroup8(); 449 break; 450 case 9: 451 if (momentary) sendMomentaryFunctionGroup9(); else sendFunctionGroup9(); 452 break; 453 case 10: 454 if (momentary) sendMomentaryFunctionGroup10(); else sendFunctionGroup10(); 455 break; 456 default: 457 break; 458 } 459 } 460 461 /** 462 * {@inheritDoc} 463 */ 464 @Override 465 public void setFunction(int functionNum, boolean newState) { 466 if (functionNum < 0 || functionNum > FUNCTION_BOOLEAN_ARRAY.length-1) { 467 log.warn("Unhandled set function number: {} {}", functionNum, this.getClass().getName()); 468 return; 469 } 470 boolean old = FUNCTION_BOOLEAN_ARRAY[functionNum]; 471 FUNCTION_BOOLEAN_ARRAY[functionNum] = newState; 472 sendFunctionGroup(functionNum,false); 473 firePropertyChange(Throttle.getFunctionString(functionNum), old, newState); 474 } 475 476 /** 477 * Update the state of a single function. Updates function value and 478 * ChangeListener. Does not send outward message TO hardware. 479 * 480 * @param fn Function Number 0-28 481 * @param state On - True, Off - False 482 */ 483 public void updateFunction(int fn, boolean state) { 484 if (fn < 0 || fn > FUNCTION_BOOLEAN_ARRAY.length-1) { 485 log.warn("Unhandled update function number: {} {}", fn, this.getClass().getName()); 486 return; 487 } 488 boolean old = FUNCTION_BOOLEAN_ARRAY[fn]; 489 FUNCTION_BOOLEAN_ARRAY[fn] = state; 490 firePropertyChange(Throttle.getFunctionString(fn), old, state); 491 } 492 493 /** 494 * Update the Momentary state of a single function. 495 * Updates function value and ChangeListener. 496 * Does not send outward message TO hardware. 497 * 498 * @param fn Momentary Function Number 0-28 499 * @param state On - True, Off - False 500 */ 501 public void updateFunctionMomentary(int fn, boolean state) { 502 if (fn < 0 || fn > FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length-1) { 503 log.warn("Unhandled update momentary function number: {} {}", fn, this.getClass().getName()); 504 return; 505 } 506 boolean old = FUNCTION_MOMENTARY_BOOLEAN_ARRAY[fn]; 507 FUNCTION_MOMENTARY_BOOLEAN_ARRAY[fn] = state; 508 firePropertyChange(Throttle.getFunctionMomentaryString(fn), old, state); 509 } 510 511 /** 512 * Send the message to set the state of functions F0, F1, F2, F3, F4. 513 * <p> 514 * This is used in the setFn implementations provided in this class, but a 515 * real implementation needs to be provided. 516 */ 517 protected void sendFunctionGroup1() { 518 log.error("sendFunctionGroup1 needs to be implemented if invoked"); 519 } 520 521 /** 522 * Send the message to set the state of functions F5, F6, F7, F8. 523 * <p> 524 * This is used in the setFn implementations provided in this class, but a 525 * real implementation needs to be provided. 526 */ 527 protected void sendFunctionGroup2() { 528 log.error("sendFunctionGroup2 needs to be implemented if invoked"); 529 } 530 531 /** 532 * Send the message to set the state of functions F9, F10, F11, F12. 533 * <p> 534 * This is used in the setFn implementations provided in this class, but a 535 * real implementation needs to be provided. 536 */ 537 protected void sendFunctionGroup3() { 538 log.error("sendFunctionGroup3 needs to be implemented if invoked"); 539 } 540 541 /** 542 * Send the message to set the state of functions F13, F14, F15, F16, F17, 543 * F18, F19, F20. 544 * <p> 545 * This is used in the setFn implementations provided in this class, but a 546 * real implementation needs to be provided. 547 */ 548 protected void sendFunctionGroup4() { 549 DccLocoAddress a = (DccLocoAddress) getLocoAddress(); 550 byte[] result = jmri.NmraPacket.function13Through20Packet( 551 a.getNumber(), a.isLongAddress(), 552 getFunction(13), getFunction(14), getFunction(15), getFunction(16), 553 getFunction(17), getFunction(18), getFunction(19), getFunction(20)); 554 555 //if the result returns as null, we should quit. 556 if (result == null) { 557 return; 558 } 559 CommandStation c; 560 if ((adapterMemo != null) && (adapterMemo.get(jmri.CommandStation.class) != null)) { 561 c = adapterMemo.get(jmri.CommandStation.class); 562 } else { 563 c = InstanceManager.getNullableDefault(CommandStation.class); 564 } 565 566 // send it 3 times 567 if (c != null) { 568 c.sendPacket(result, 3); 569 } else { 570 log.error("Can't send F13-F20 since no command station defined"); 571 } 572 } 573 574 /** 575 * Send the message to set the state of functions F21, F22, F23, F24, F25, 576 * F26, F27, F28. 577 * <p> 578 * This is used in the setFn implementations provided in this class, but a 579 * real implementation needs to be provided. 580 */ 581 protected void sendFunctionGroup5() { 582 DccLocoAddress a = (DccLocoAddress) getLocoAddress(); 583 byte[] result = jmri.NmraPacket.function21Through28Packet( 584 a.getNumber(), a.isLongAddress(), 585 getFunction(21), getFunction(22), getFunction(23), getFunction(24), 586 getFunction(25), getFunction(26), getFunction(27), getFunction(28)); 587 //if the result returns as null, we should quit. 588 if (result == null) { 589 return; 590 } 591 CommandStation c; 592 if ((adapterMemo != null) && (adapterMemo.get(jmri.CommandStation.class) != null)) { 593 c = adapterMemo.get(jmri.CommandStation.class); 594 } else { 595 c = InstanceManager.getNullableDefault(CommandStation.class); 596 } 597 598 // send it 3 times 599 if (c != null) { 600 c.sendPacket(result, 3); 601 } else { 602 log.error("Can't send F21-F28 since no command station defined"); 603 } 604 } 605 606 /** 607 * Send the message to set the state of functions F29 - F36. 608 * <p> 609 * This is used in the setFn implementations provided in this class, but a 610 * real implementation needs to be provided. 611 */ 612 protected void sendFunctionGroup6() { 613 log.error("sendFunctionGroup6 needs to be implemented if invoked"); 614 } 615 616 /** 617 * Send the message to set the state of functions F37 - F44. 618 * <p> 619 * This is used in the setFn implementations provided in this class, but a 620 * real implementation needs to be provided. 621 */ 622 protected void sendFunctionGroup7() { 623 log.error("sendFunctionGroup7 needs to be implemented if invoked"); 624 } 625 626 /** 627 * Send the message to set the state of functions F45 - F52. 628 * <p> 629 * This is used in the setFn implementations provided in this class, but a 630 * real implementation needs to be provided. 631 */ 632 protected void sendFunctionGroup8() { 633 log.error("sendFunctionGroup8 needs to be implemented if invoked"); 634 } 635 636 /** 637 * Send the message to set the state of functions F53 - F60. 638 * <p> 639 * This is used in the setFn implementations provided in this class, but a 640 * real implementation needs to be provided. 641 */ 642 protected void sendFunctionGroup9() { 643 log.error("sendFunctionGroup9 needs to be implemented if invoked"); 644 } 645 646 /** 647 * Send the message to set the state of functions F61 - F68. 648 * <p> 649 * This is used in the setFn implementations provided in this class, but a 650 * real implementation needs to be provided. 651 */ 652 protected void sendFunctionGroup10() { 653 log.error("sendFunctionGroup10 needs to be implemented if invoked"); 654 } 655 656 /** 657 * Sets Momentary Function and sends to layout. 658 * {@inheritDoc} 659 */ 660 @Override 661 public void setFunctionMomentary(int momFuncNum, boolean state){ 662 if (momFuncNum < 0 || momFuncNum > FUNCTION_MOMENTARY_BOOLEAN_ARRAY.length-1) { 663 log.warn("Unhandled set momentary function number: {} {}", momFuncNum, this.getClass().getName()); 664 return; 665 } 666 boolean old = FUNCTION_MOMENTARY_BOOLEAN_ARRAY[momFuncNum]; 667 FUNCTION_MOMENTARY_BOOLEAN_ARRAY[momFuncNum] = state; 668 sendFunctionGroup(momFuncNum,true); 669 firePropertyChange(Throttle.getFunctionMomentaryString(momFuncNum), old, state); 670 } 671 672 /** 673 * Send the message to set the momentary state of functions F0, F1, F2, F3, 674 * F4. 675 * <p> 676 * This is used in the setFnMomentary implementations provided in this 677 * class, a real implementation needs to be provided if the hardware 678 * supports setting functions momentary. 679 */ 680 protected void sendMomentaryFunctionGroup1() { 681 } 682 683 /** 684 * Send the message to set the momentary state of functions F5, F6, F7, F8. 685 * <p> 686 * This is used in the setFnMomentary implementations provided in this 687 * class, but a real implementation needs to be provided if the hardware 688 * supports setting functions momentary. 689 */ 690 protected void sendMomentaryFunctionGroup2() { 691 } 692 693 /** 694 * Send the message to set the Momentary state of functions F9, F10, F11, 695 * F12 696 * <p> 697 * This is used in the setFnMomentary implementations provided in this 698 * class, but a real implementation needs to be provided if the hardware 699 * supports setting functions momentary. 700 */ 701 protected void sendMomentaryFunctionGroup3() { 702 } 703 704 /** 705 * Send the message to set the Momentary state of functions F13, F14, F15, 706 * F16, F17, F18, F19, F20 707 * <p> 708 * This is used in the setFnMomentary implementations provided in this 709 * class, but a real implementation needs to be provided if the hardware 710 * supports setting functions momentary. 711 */ 712 protected void sendMomentaryFunctionGroup4() { 713 } 714 715 /** 716 * Send the message to set the Momentary state of functions F21, F22, F23, 717 * F24, F25, F26, F27, F28 718 * <p> 719 * This is used in the setFnMomentary implementations provided in this 720 * class, but a real implementation needs to be provided if the hardware 721 * supports setting functions momentary. 722 */ 723 protected void sendMomentaryFunctionGroup5() { 724 } 725 726 /** 727 * Send the message to set the Momentary state of functions F29 - F36 728 * <p> 729 * This is used in the setFnMomentary implementations provided in this 730 * class, but a real implementation needs to be provided if the hardware 731 * supports setting functions momentary. 732 */ 733 protected void sendMomentaryFunctionGroup6() { 734 } 735 736 /** 737 * Send the message to set the Momentary state of functions F37 - F44 738 * <p> 739 * This is used in the setFnMomentary implementations provided in this 740 * class, but a real implementation needs to be provided if the hardware 741 * supports setting functions momentary. 742 */ 743 protected void sendMomentaryFunctionGroup7() { 744 } 745 746 /** 747 * Send the message to set the Momentary state of functions F45 - 52 748 * <p> 749 * This is used in the setFnMomentary implementations provided in this 750 * class, but a real implementation needs to be provided if the hardware 751 * supports setting functions momentary. 752 */ 753 protected void sendMomentaryFunctionGroup8() { 754 } 755 756 /** 757 * Send the message to set the Momentary state of functions F53 - F60 758 * <p> 759 * This is used in the setFnMomentary implementations provided in this 760 * class, but a real implementation needs to be provided if the hardware 761 * supports setting functions momentary. 762 */ 763 protected void sendMomentaryFunctionGroup9() { 764 } 765 766 /** 767 * Send the message to set the Momentary state of functions F61 - F68 768 * <p> 769 * This is used in the setFnMomentary implementations provided in this 770 * class, but a real implementation needs to be provided if the hardware 771 * supports setting functions momentary. 772 */ 773 protected void sendMomentaryFunctionGroup10() { 774 } 775 776 /** 777 * Set the speed step value. Default should be 128 speed step mode in most 778 * cases. 779 * <p> 780 * Specific implementations should override this function. 781 * 782 * @param mode the current speed step mode 783 */ 784 @Override 785 public void setSpeedStepMode(SpeedStepMode mode) { 786 log.debug("Speed Step Mode Change from:{} to:{}", speedStepMode, mode); 787 firePropertyChange(SPEEDSTEPS, speedStepMode, speedStepMode = mode); 788 } 789 790 @Override 791 public SpeedStepMode getSpeedStepMode() { 792 return speedStepMode; 793 } 794 795 long durationRunning = 0; 796 protected long start; 797 798 /** 799 * Processes updated speed from subclasses. Tracks total operating time for 800 * the roster entry by starting the clock if speed is non-zero or stopping 801 * the clock otherwise. 802 * 803 * @param speed the current speed 804 */ 805 protected synchronized void record(float speed) { 806 if (re == null) { 807 return; 808 } 809 if (speed == 0) { 810 stopClock(); 811 } else { 812 startClock(); 813 } 814 } 815 816 protected synchronized void startClock() { 817 if (start == 0) { 818 start = System.currentTimeMillis(); 819 } 820 } 821 822 void stopClock() { 823 if (start == 0) { 824 return; 825 } 826 long stop = System.currentTimeMillis(); 827 //Set running duration in seconds 828 durationRunning = durationRunning + ((stop - start) / 1000); 829 start = 0; 830 } 831 832 protected synchronized void finishRecord() { 833 if (re == null) { 834 return; 835 } 836 stopClock(); 837 String currentDurationString = re.getAttribute(RosterEntry.ATTRIBUTE_OPERATING_DURATION); 838 long currentDuration = 0; 839 if (currentDurationString == null) { 840 currentDurationString = "0"; 841 log.info("operating duration for {} starts as zero", getLocoAddress()); 842 } 843 try { 844 currentDuration = Long.parseLong(currentDurationString); 845 } catch (NumberFormatException e) { 846 log.warn("current stored duration is not a valid number \"{} \"", currentDurationString); 847 } 848 currentDuration = currentDuration + durationRunning; 849 re.putAttribute(RosterEntry.ATTRIBUTE_OPERATING_DURATION, "" + currentDuration); 850 re.putAttribute(RosterEntry.ATTRIBUTE_LAST_OPERATED, new StdDateFormat().format(new Date())); 851 //Only store if the roster entry isn't open. 852 if (!re.isOpen()) { 853 re.store(); 854 } else { 855 log.warn("Roster Entry {} running time not saved as entry is already open for editing", re.getId()); 856 } 857 re = null; 858 } 859 860 @GuardedBy("this") 861 BasicRosterEntry re = null; 862 863 @Override 864 public synchronized void setRosterEntry(BasicRosterEntry re) { 865 this.re = re; 866 } 867 868 @Override 869 public synchronized BasicRosterEntry getRosterEntry() { 870 return re; 871 } 872 873 /** 874 * Get an integer speed for the given raw speed value. This is a convenience 875 * method that calls {@link #intSpeed(float, int)} with a maxStep of 127. 876 * 877 * @param speed the speed as a percentage of maximum possible speed; 878 * negative values indicate a need for an emergency stop 879 * @return an integer in the range 0-127 880 */ 881 protected int intSpeed(float speed) { 882 return intSpeed(speed, 127); 883 } 884 885 /** 886 * Get an integer speed for the given raw speed value. 887 * 888 * @param speed the speed as a percentage of maximum possible speed; 889 * negative values indicate a need for an emergency stop 890 * @param steps number of possible speeds; values less than 2 will cause 891 * errors 892 * @return an integer in the range 0-steps 893 */ 894 protected static int intSpeed(float speed, int steps) { 895 // test that speed is < 0 for emergency stop since calculation of 896 // value returns 0 for some values of -1 < rawSpeed < 0 897 if (speed < 0) { 898 return 1; // emergency stop 899 } 900 901 // Stretch speed input to full output range 902 // Since Emergency Stop (estop) is speed 1, subtract 1 from steps 903 speed *= (steps - 1); 904 // convert to integer by rounding 905 int value = Math.round(speed); 906 907 // Only return stop if value is actually 0, jump to first speed 908 // step for small positive inputs. 909 // speeds (at this point) larger than 0.5f are already handled 910 // by the rounding above. 911 if (speed > 0.0f && speed <= 0.5f) { 912 value = 1; 913 } 914 915 if (value < 0) { 916 // if we get here, something is wrong and needs to be reported. 917 Exception ex = new Exception("Please send logs to the JMRI developers."); 918 log.error("Error calculating speed.", ex); 919 return 1; // return estop anyway 920 } else if (value >= steps) { 921 return steps; // maximum possible speed 922 } else if (value > 0) { 923 return value + 1; // add 1 to the value to avoid the estop 924 } else { 925 return 0; // non-emergency stop 926 } 927 } 928 929 // initialize logging 930 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractThrottle.class); 931 932}