001package jmri.jmrix.can.cbus; 002 003import jmri.DccLocoAddress; 004import jmri.LocoAddress; 005import jmri.SpeedStepMode; 006import jmri.ThrottleManager; 007import jmri.jmrix.AbstractThrottle; 008import jmri.jmrix.can.CanSystemConnectionMemo; 009 010/** 011 * An implementation of DccThrottle via AbstractThrottle with code specific to a 012 * CBUS connection. 013 * <p> 014 * Speed in the Throttle interfaces and AbstractThrottle is a float, but in CBUS 015 * is normally an int with values from 0 to 127. 016 * <table> 017 * <caption>CBUS 128 Speed Steps</caption> 018 * <tr><td>setSpeedSetting</td><td>CBUS DSPD</td><td>Translated</td><td>Throttle</td></tr> 019 * <tr><td>0</td><td> 0 </td><td> Speed 0 </td><td> 0 % </td></tr> 020 * <tr><td>-1</td><td> 1 </td><td> E Stop </td><td> 0 % </td></tr> 021 * <tr><td>0.007937</td><td> 2 </td><td> Speed 1 </td><td> 1/126 % </td></tr> 022 * <tr><td>0.015873</td><td> 3 </td><td> Speed 2 </td><td> 2/126 % </td></tr> 023 * <tr><td>0.984127</td><td> 125 </td><td> Speed 124 </td><td> 124/126 % </td></tr> 024 * <tr><td>0.992063</td><td> 126 </td><td> Speed 125 </td><td> 125/126 % </td></tr> 025 * <tr><td>1</td><td> 127 </td><td> Speed 126 </td><td> 100 % </td></tr> 026 * </table> 027 * 028 * <table> 029 * <caption>CBUS 28 Speed Steps</caption> 030 * <tr><td>CBUS DSPD</td><td>Translated</td><td>Throttle</td></tr> 031 * <tr><td> 0 </td><td> Speed 0 Encoding 1 </td><td> 0 % </td></tr> 032 * <tr><td> 1 </td><td> Speed 0 Encoding 2 </td><td> 0 % </td></tr> 033 * <tr><td> 2 </td><td> E Stop Encoding 1 </td><td> 0 % </td></tr> 034 * <tr><td> 3 </td><td> E Stop Encoding 2 </td><td> 0 % </td></tr> 035 * <tr><td> 4 </td><td> Speed 1 </td><td> 1/28 % </td></tr> 036 * <tr><td> 5 </td><td> Speed 2 </td><td> 2/28 % </td></tr> 037 * <tr><td> 29 </td><td> Speed 26 </td><td> 26/28 % </td></tr> 038 * <tr><td> 30 </td><td> Speed 27 </td><td> 27/28 % </td></tr> 039 * <tr><td> 31 </td><td> Speed 28 </td><td> 100 % </td></tr> 040 * </table> 041 * 042 * <table> 043 * <caption>CBUS 14 Speed Steps</caption> 044 * <tr><td>CBUS DSPD</td><td>Translated</td><td>Throttle</td></tr> 045 * <tr><td> 0 </td><td> Speed 0 </td><td> 0 % </td></tr> 046 * <tr><td> 1 </td><td> E Stop </td><td> 0 % </td></tr> 047 * <tr><td> 2 </td><td> Speed 1 </td><td> 1/14 % </td></tr> 048 * <tr><td> 3 </td><td> Speed 2 </td><td> 2/14 % </td></tr> 049 * <tr><td> 13 0x0D </td><td> Speed 12 </td><td> 12/14 % </td></tr> 050 * <tr><td> 14 0x0E </td><td> Speed 13 </td><td> 13/14 % </td></tr> 051 * <tr><td> 15 0x0F </td><td> Speed 14 </td><td> 100 % </td></tr> 052 * </table> 053 * 054 * @author Andrew Crosland Copyright (C) 2009 055 */ 056public class CbusThrottle extends AbstractThrottle { 057 058 private CbusCommandStation cs = null; 059 private int _handle = -1; 060 private DccLocoAddress dccAddress = null; 061 private boolean _isStolen; 062 private int _recoveryAttempts; 063 064 /** 065 * Constructor 066 * 067 * @param memo System Connection 068 * @param address The address this throttle relates to. 069 * @param handle the Session ID for the Throttle 070 */ 071 public CbusThrottle(CanSystemConnectionMemo memo, LocoAddress address, int handle) { 072 super(memo, CbusConstants.MAX_FUNCTIONS); 073 log.debug("creating new CbusThrottle address {} handle {}",address,handle); 074 if (!( address instanceof DccLocoAddress ) ){ 075 log.error("{} is not a DccLocoAddress",address); 076 return; 077 } 078 cs = (CbusCommandStation) adapterMemo.get(jmri.CommandStation.class); 079 _handle = handle; 080 _isStolen = false; 081 _recoveryAttempts = 0; 082 083 // cache settings 084 synchronized(this) { 085 this.speedSetting = 0; 086 } 087 // Functions 0-28 default to false 088 this.dccAddress = (DccLocoAddress) address; 089 this.isForward = true; 090 this.speedStepMode = SpeedStepMode.NMRA_DCC_128; 091 092 // start periodically sending keep alives, to keep this attached 093 log.debug("Start Throttle refresh"); 094 startRefresh(); 095 } 096 097 /** 098 * Set initial throttle values as taken from PLOC reply from hardware 099 * 100 * @param speed including direction flag 101 * @param f0f4 Functions 0-4 102 * @param f5f8 Functions 5-8 103 * @param f9f12 Functions 9-12 104 */ 105 protected void throttleInit(int speed, int f0f4, int f5f8, int f9f12) { 106 log.debug("Setting throttle initial values"); 107 updateSpeedSetting( speed & 0x7f ); 108 updateIsForward ( (speed & 0x80) == 0x80 ); 109 updateFunctionGroup(1,f0f4); 110 updateFunctionGroup(2,f5f8); 111 updateFunctionGroup(3,f9f12); 112 } 113 114 /** 115 * setSpeedStepMode - set the speed step value. 116 * <p> 117 * Overridden to capture mode changes to be forwarded to the hardware. 118 * New throttles default to 128 step mode. 119 * CBUS Command stations also default to 128SS so this does not 120 * need to be sent if unchanged. 121 * 122 * @param stepMode the current speed step mode - default should be 128 123 * speed step mode in most cases 124 */ 125 @Override 126 public void setSpeedStepMode(SpeedStepMode stepMode) { 127 if (speedStepMode==stepMode){ 128 return; 129 } 130 super.setSpeedStepMode(stepMode); 131 int mode; 132 switch (speedStepMode) { 133 case NMRA_DCC_28: 134 mode = CbusConstants.CBUS_SS_28; 135 break; 136 case NMRA_DCC_14: 137 mode = CbusConstants.CBUS_SS_14; 138 break; 139 default: 140 mode = CbusConstants.CBUS_SS_128; 141 break; 142 } 143 cs.setSpeedSteps(_handle, mode); 144 } 145 146 /** 147 * Convert a CBUS speed integer to a float speed value 148 * @param lSpeed -1 to 127 149 * @return float value -1 to 1 150 */ 151 protected float floatSpeed(int lSpeed) { 152 if (this.getSpeedStepMode()== SpeedStepMode.NMRA_DCC_28) { 153 float toReturn = 0.f; 154 switch (lSpeed) { 155 case 0: 156 case 1: 157 break; 158 case 2: 159 case 3: 160 toReturn = -1.f; // estop 161 break; 162 default: 163 toReturn = ((lSpeed - 3) / (float) speedStepMode.numSteps ); 164 } 165 return Math.min(toReturn, 1.0f); // return smallest value 166 } 167 168 switch (lSpeed) { 169 case 0: 170 return 0.f; 171 case 1: 172 return -1.f; // estop 173 default: 174 return ((lSpeed - 1) / (float) speedStepMode.numSteps ); 175 } 176 } 177 178 /** 179 * Send the CBUS message to set the state of functions F0, F1, F2, F3, F4. 180 */ 181 @Override 182 protected void sendFunctionGroup1() { 183 sendFunctionGroup(1); 184 } 185 186 /** 187 * Send the CBUS message to set the state of functions F5, F6, F7, F8. 188 */ 189 @Override 190 protected void sendFunctionGroup2() { 191 sendFunctionGroup(2); 192 } 193 194 /** 195 * Send the CBUS message to set the state of functions F9, F10, F11, F12. 196 */ 197 @Override 198 protected void sendFunctionGroup3() { 199 sendFunctionGroup(3); 200 } 201 202 /** 203 * Send the CBUS message to set the state of functions F13, F14, F15, F16, 204 * F17, F18, F19, F20 205 */ 206 @Override 207 protected void sendFunctionGroup4() { 208 sendFunctionGroup(4); 209 } 210 211 /** 212 * Send the CBUS message to set the state of functions F21, F22, F23, F24, 213 * F25, F26, F27, F28 214 */ 215 @Override 216 protected void sendFunctionGroup5() { 217 sendFunctionGroup(5); 218 } 219 220 /** 221 * Send the CBUS message to set the state of functions F29 - F36 222 */ 223 @Override 224 protected void sendFunctionGroup6() { 225 sendFunctionGroup(6); 226 } 227 228 protected void sendFunctionGroup(int group) { 229 int totVal = 0; 230 for ( int i=0; i<CbusConstants.MAX_FUNCTIONS; i++ ){ 231 if (FUNCTION_GROUPS[i]==group && getFunction(i)){ 232 totVal += CbusConstants.CBUS_FUNCTION_BITS[i]; 233 } 234 } 235 cs.setFunctions(group, _handle, totVal); 236 } 237 238 protected void updateFunctionGroup(int group, int fns) { 239 for ( int i=0; i<CbusConstants.MAX_FUNCTIONS; i++ ){ 240 if (FUNCTION_GROUPS[i]==group){ 241 updateFunction( i, (fns & CbusConstants.CBUS_FUNCTION_BITS[i]) == CbusConstants.CBUS_FUNCTION_BITS[i] ); 242 } 243 } 244 } 245 246 /** 247 * Set the speed. 248 * <p> 249 * This intentionally skips the emergency stop value of 1. 250 * 251 * @param speed Number from 0 to 1; less than zero is emergency stop 252 */ 253 @Override 254 public synchronized void setSpeedSetting(float speed, boolean allowDuplicates, boolean allowDuplicatesOnStop) { 255 log.debug("setSpeedSetting({})", speed); 256 float oldSpeed = this.speedSetting; 257 this.speedSetting = speed; 258 if (speed < 0) { 259 this.speedSetting = -1.f; 260 } 261 262 setDispatchActive(this.speedSetting > 0); 263 264 if ( oldCbusSpeed != getCbusSpeedDirection() || allowDuplicates || ( speed==0 && allowDuplicatesOnStop ) ) { 265 sendToLayout(); 266 firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting); 267 record(this.speedSetting); // float 268 } 269 } 270 271 private synchronized int getCbusSpeedFromFloat(){ 272 switch (speedStepMode) { 273 case NMRA_DCC_28: 274 int ints = intSpeed( this.speedSetting, 29 ); 275 // speed 1 starts at cbus 4, not 2. 276 if (ints>1){ 277 ints += 2; 278 } 279 return ints; 280 case NMRA_DCC_14: 281 return intSpeed( this.speedSetting, 15 ); 282 case NMRA_DCC_128: 283 default: 284 // cbus speeds 0x00 to 0x80 , excluding 0x01 for estop gives 127 possible values including 0. 285 return intSpeed( this.speedSetting ); 286 } 287 } 288 289 private synchronized int getCbusSpeedDirection(){ 290 int speed = getCbusSpeedFromFloat(); 291 if (this.isForward) { 292 speed |= 0x80; 293 } 294 return speed; 295 } 296 297 private int oldCbusSpeed = -2; 298 299 // following a speed or direction change, sends to layout 300 private void sendToLayout() { 301 oldCbusSpeed = getCbusSpeedDirection(); 302 303 log.debug("Sending speed/dir for speed: {}",oldCbusSpeed); 304 // reset timeout 305 mRefreshTimer.stop(); 306 mRefreshTimer.setRepeats(true); 307 mRefreshTimer.start(); 308 if (cs != null ) { 309 cs.setSpeedDir(_handle, oldCbusSpeed); 310 } 311 } 312 313 /** 314 * Update the throttles speed setting without sending to hardware. Used to 315 * support CBUS sharing by taking speed received <b>from</b> the hardware in 316 * an OPC_DSPD message. 317 * <p> 318 * No compensation required for a direction flag 319 * @param speed integer speed value 320 */ 321 protected synchronized void updateSpeedSetting(int speed) { 322 323 log.debug("Updated speed/dir for speed:{}",speed); 324 325 float oldSpeed = this.speedSetting; 326 this.speedSetting = floatSpeed(speed); 327 if (speed < 0) { 328 this.speedSetting = -1.f; 329 } 330 331 setDispatchActive(this.speedSetting > 0); 332 333 if (Math.abs(oldSpeed - this.speedSetting) > 0.0001) { // an increment FROM CBUS will always be > 0.00793 334 firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting); 335 record(this.speedSetting); // float 336 } 337 } 338 339 /** 340 * Set the direction and reset speed. 341 * Forwards to the layout 342 * {@inheritDoc} 343 */ 344 @Override 345 public void setIsForward(boolean forward) { 346 boolean old = this.isForward; 347 this.isForward = forward; 348 if (old != this.isForward) { 349 sendToLayout(); 350 firePropertyChange(ISFORWARD, old, isForward); 351 } 352 } 353 354 /** 355 * Update the throttles direction without sending to hardware.Used to 356 * support CBUS sharing by taking direction received <b>from</b> the 357 * hardware in an OPC_DSPD message. 358 * @param forward True if Forward, else False 359 */ 360 protected void updateIsForward(boolean forward){ 361 super.setIsForward(forward); 362 } 363 364 /** 365 * {@inheritDoc} 366 */ 367 @Override 368 public String toString() { 369 return getLocoAddress().toString(); 370 } 371 372 /** 373 * Return the handle for this throttle 374 * 375 * @return integer session handle 376 */ 377 protected int getHandle() { 378 return _handle; 379 } 380 381 /** 382 * Set the handle for this throttle 383 * <p> 384 * This is normally done on Throttle Construction but certain 385 * operations, eg. recovering from an external steal 386 * may need to change this. 387 * @param newHandle session handle 388 */ 389 protected void setHandle(int newHandle){ 390 _handle = newHandle; 391 } 392 393 /** 394 * Set Throttle Stolen Flag 395 * <p> 396 * This is false on Throttle Construction but certain 397 * operations may need to change this, eg. an external steal. 398 * <p> 399 * Sends IsAvailable Property Change Notification 400 * @param isStolen true if Throttle has been stolen, else false 401 */ 402 protected void setStolen(boolean isStolen){ 403 if (isStolen != _isStolen){ 404 firePropertyChange("IsAvailable", isStolen, _isStolen); // PCL is opposite of local boolean 405 _isStolen = isStolen; 406 } 407 if (isStolen){ // stop keep-alive messages 408 if ( mRefreshTimer != null ) { 409 mRefreshTimer.stop(); 410 } 411 mRefreshTimer = null; 412 } 413 else { 414 startRefresh(); // resume keep-alive messages 415 } 416 } 417 418 /** 419 * Get Throttle Stolen Flag 420 * <p> 421 * This is false on Throttle Construction but certain 422 * operations may need to change this, eg. an external steal. 423 * @return true if Throttle has been stolen, else false 424 */ 425 protected boolean isStolen(){ 426 return _isStolen; 427 } 428 429 /** 430 * Get the number of external steal recovery attempts 431 * @return Number of attempts since last reset 432 */ 433 protected int getNumRecoverAttempts(){ 434 return _recoveryAttempts; 435 } 436 437 /** 438 * Increase a count of external steal recovery attempts 439 */ 440 protected void increaseNumRecoverAttempts(){ 441 _recoveryAttempts++; 442 } 443 444 /** 445 * Reset count of recovery attempts 446 */ 447 protected void resetNumRecoverAttempts(){ 448 _recoveryAttempts = 0; 449 } 450 451 /** 452 * Release session from a command station 453 * ie. throttle with clean full dispose called from releaseThrottle 454 */ 455 protected void releaseFromCommandStation(){ 456 if ( cs != null ) { 457 cs.releaseSession(_handle); 458 } 459 } 460 461 /** 462 * Dispose when finished with this object. After this, further usage of this 463 * Throttle object will result in a JmriException. 464 */ 465 @Override 466 public void throttleDispose() { 467 log.debug("dispose"); 468 469 finishRecord(); 470 471 notifyThrottleDisconnect(); 472 473 // stop timeout 474 if ( mRefreshTimer != null ) { 475 mRefreshTimer.stop(); 476 } 477 mRefreshTimer = null; 478 cs = null; 479 _handle = -1; 480 481 } 482 483 private javax.swing.Timer mRefreshTimer; 484 485 // CBUS command stations expect DSPD per sesison every 4s 486 protected final void startRefresh() { 487 mRefreshTimer = new javax.swing.Timer(4000, (java.awt.event.ActionEvent e) -> keepAlive()); 488 mRefreshTimer.setRepeats(true); // refresh until stopped by dispose 489 mRefreshTimer.start(); 490 } 491 492 /** 493 * Internal routine to resend the speed on a timeout 494 */ 495 private synchronized void keepAlive() { 496 if (cs != null) { // cs can be null if in process of terminating? 497 cs.sendKeepAlive(_handle); 498 499 // reset timeout 500 mRefreshTimer.stop(); 501 mRefreshTimer.setRepeats(true); // refresh until stopped by dispose 502 mRefreshTimer.start(); 503 } 504 } 505 506 /** 507 * {@inheritDoc} 508 */ 509 @Override 510 public LocoAddress getLocoAddress() { 511 return dccAddress; 512 } 513 514 /** 515 * Adds extra check for num of this JMRI throttle users before notifying 516 * and makes sure these always get sent as a pair 517 * the abstracts only send to ThrottleListeners if value has been changed 518 * @param newval set true if dispatch can be enabled, else false 519 */ 520 protected void setDispatchActive( boolean newval){ 521 522 // feature disabled if command station not listed in CBUS node table, 523 // could be CANCMD v3 or in test 524 if ( cs == null ) { 525 return; 526 } 527 if ( cs.getMasterCommandStation() == null ) { 528 return; 529 } 530 531 if ( newval ) { 532 int numThrottles = adapterMemo.get(ThrottleManager.class).getThrottleUsageCount(dccAddress); 533 log.debug("numThrottles {}",numThrottles); 534 if ( numThrottles < 2 ){ 535 notifyThrottleReleaseEnabled(false); 536 notifyThrottleDispatchEnabled(true); 537 return; 538 } 539 } 540 notifyThrottleReleaseEnabled(true); 541 notifyThrottleDispatchEnabled(false); 542 } 543 544 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CbusThrottle.class); 545 546}