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