001package jmri.jmrix.loconet; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import javax.annotation.CheckForNull; 005import jmri.DccLocoAddress; 006import jmri.DccThrottle; 007import jmri.LocoAddress; 008import jmri.SpeedStepMode; 009import jmri.jmrix.AbstractThrottle; 010import org.slf4j.Logger; 011import org.slf4j.LoggerFactory; 012import jmri.ThrottleListener; 013 014/** 015 * An implementation of DccThrottle via AbstractThrottle with code specific to a 016 * LocoNet connection. 017 * <p> 018 * Speed in the Throttle interfaces and AbstractThrottle is a float, but in 019 * LocoNet is an int with values from 0 to 127. 020 * 021 * @author Glen Oberhauser, Bob Jacobsen Copyright (C) 2003, 2004 022 * @author Stephen Williams Copyright (C) 2008 023 * @author B. Milhaupt, Copyright (C) 2018 024 */ 025public class LocoNetThrottle extends AbstractThrottle implements SlotListener { 026 027 protected LocoNetSlot slot; 028 protected LocoNetInterface network; 029 protected LnThrottleManager throttleManager; 030 protected int address; 031 032 // members to record the last known spd/dirf/snd bytes AS READ FROM THE LAYOUT!! 033 protected int layout_spd; 034 protected int layout_dirf; 035 protected int layout_snd; 036 protected int layout_stat1 = 0; 037 038 // with extended slots the slots may not have been updated by the echo 039 // before the next message needs sending.So we must save and send what 040 // we believe to be the correct speed and direction. 041 // remember in expanded mode 2 throttle cannot be in control of a loco 042 043 protected int new_spd; 044 protected long new_spd_lastupdated; 045 protected boolean new_isFwd; 046 protected long new_isFwd_lastupdated; 047 048 // slot status to be warned if slot released or dispatched 049 protected int slotStatus; 050 protected boolean isDisposing = false; 051 052 /** 053 * Constructor 054 * 055 * @param memo connection details 056 * @param slot The LocoNetSlot this throttle will talk on. 057 */ 058 public LocoNetThrottle(LocoNetSystemConnectionMemo memo, LocoNetSlot slot) { 059 super(memo, 69); // supports up to F68 060 this.slot = slot; 061 slot.setIsInitialized(false); 062 network = memo.getLnTrafficController(); 063 throttleManager = (LnThrottleManager)memo.getThrottleManager(); 064 065 // save last known layout state for spd/dirf/snd so we can 066 // avoid race condition if another LocoNet process queries 067 // our slot while we are in the act of changing it. 068 layout_spd = slot.speed(); 069 layout_dirf = slot.dirf(); 070 layout_snd = slot.snd(); 071 072 // cache settings 073 synchronized(this) { 074 this.speedSetting = floatSpeed(slot.speed()); 075 } 076 for (int i = 0; i < 29; i++) { 077 super.updateFunction(i,slot.isFunction(i)); 078 } 079 080 // for LocoNet throttles, the default is f2 momentary (for the horn) 081 // all other functions are continuos (as set in AbstractThrottle). 082 super.updateFunctionMomentary(2, true); 083 084 this.address = slot.locoAddr(); 085 this.isForward = slot.isForward(); 086 this.slotStatus = slot.slotStatus(); 087 088 switch (slot.decoderType()) { 089 case LnConstants.DEC_MODE_128: 090 case LnConstants.DEC_MODE_128A: 091 setSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 092 break; 093 case LnConstants.DEC_MODE_28: 094 case LnConstants.DEC_MODE_28A: 095 case LnConstants.DEC_MODE_28TRI: 096 setSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 097 break; 098 case LnConstants.DEC_MODE_14: 099 setSpeedStepMode(SpeedStepMode.NMRA_DCC_14); 100 break; 101 default: 102 log.warn("Unhandled decoder type: {}", slot.decoderType()); 103 break; 104 } 105 106 // listen for changes 107 slot.addSlotListener(this); 108 109 network.sendLocoNetMessage(slot.writeNullMove()); 110 111 // start periodically sending the speed, to keep this 112 // attached 113 startRefresh(); 114 log.debug("constructed a new throttle using slot {} for loco address {}", slot.getSlot(), slot.locoAddr()); 115 } 116 117 /** 118 * Convert a LocoNet speed integer to a float speed value 119 * 120 * @param lSpeed LocoNet style speed value 121 * @return speed as float 0->1.0, or -1.0 to indicate E-Stop 122 */ 123 protected float floatSpeed(int lSpeed) { 124 log.debug("speed (int) is {}", lSpeed); 125 if (lSpeed == 0) { 126 return 0.f; 127 } else if (lSpeed == 1) { 128 return -1.f; // estop 129 } 130 if (getSpeedStepMode() == SpeedStepMode.NMRA_DCC_28) { 131 if (lSpeed <= 15) //Value less than 15 is in the stop/estop range bracket 132 { 133 return 0.f; 134 } 135 return (((lSpeed - 12) / 4f) / 28.f); 136 } else if (getSpeedStepMode() == SpeedStepMode.NMRA_DCC_14) { 137 if (lSpeed <= 15) //Value less than 15 is in the stop/estop range bracket 138 { 139 return 0.f; 140 } 141 return ((lSpeed - 8) / 8f) / 14.f; 142 } else { 143 return ((lSpeed - 1) / 126.f); 144 } 145 } 146 147 /** 148 * Computes the integer speed value from a float. 149 * <p> 150 * Values of less than 0 indicate Emergency Stop. 151 * <p> 152 * Value of 0.0 indicates stop. 153 * <p> 154 * Values between 0.0+ and 1.0 imply speed step values between 2 and the 155 * maximum value allowed for the loco's speed step mode. 156 * 157 * @param fSpeed is the floating-point speed value to be converted 158 * @return an integer which represents the speed step value 159 */ 160 @Override 161 protected int intSpeed(float fSpeed) { 162 log.debug("intSpeed speed is {}", fSpeed); 163 int speed = super.intSpeed(fSpeed); 164 if (speed <= 1) { 165 return speed; // return idle and emergency stop 166 } 167 switch (this.getSpeedStepMode()) { 168 case NMRA_DCC_28: 169 case MOTOROLA_28: 170 return (int) ((fSpeed * 28) * 4) + 12; 171 case NMRA_DCC_14: 172 return (int) ((fSpeed * 14) * 8) + 8; 173 case NMRA_DCC_128: 174 return speed; 175 default: 176 log.warn("Unhandled speed step: {}", this.getSpeedStepMode()); 177 break; 178 } 179 return speed; 180 } 181 182 /** 183 * Constants to represent Function Groups. 184 * <p> 185 * The are the same groupings for both normal Functions and Momentary. 186 */ 187 private static final int[] EXP_FUNCTION_GROUPS = new int[]{ 188 1, 1, 1, 1, 1, 1, 1, /** 0-6 */ 189 2, 2, 2, 2, 2, 2, 2, /** 7 - 13 */ 190 3, 3, 3, 3, 3, 3, 3, /** 14 -20 */ 191 4, 4, 4, 4, 4, 4, 4, 4, /** 21 - 28 */ 192 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 29 - 69 193 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 29 - 69 194 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 29 - 69 195 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // 29 - 69 196 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 // 29 - 69 197 }; 198 199 /** 200 * Send whole (DCC) Function Group for a particular function number. 201 * @param functionNum Function Number 202 * @param momentary False to send normal function status, true to send momentary. 203 */ 204 @Override 205 protected void sendFunctionGroup(int functionNum, boolean momentary){ 206 if (slot.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) { 207 super.sendFunctionGroup(functionNum, momentary); 208 return; 209 } 210 switch (EXP_FUNCTION_GROUPS[functionNum]) { 211 case 1: 212 if (momentary) sendMomentaryFunctionGroup1(); else sendExpFunctionGroup1(); 213 break; 214 case 2: 215 if (momentary) sendMomentaryFunctionGroup2(); else sendExpFunctionGroup2(); 216 break; 217 case 3: 218 if (momentary) sendMomentaryFunctionGroup3(); else sendExpFunctionGroup3(); 219 break; 220 case 4: 221 if (momentary) sendMomentaryFunctionGroup4(); else sendExpFunctionGroup4(); 222 break; 223 case 5: 224 // send as regular function operations 225 super.sendFunctionGroup(functionNum, momentary); 226 break; 227 default: 228 break; 229 } 230 } 231 232 /** 233 * Send the LocoNet message to set the state of locomotive direction and 234 * functions F0, F1, F2, F3, F4 235 * Unfortunately this is used by all throttles to send direction changes, but the expanded slots dont use this 236 * for direction changes, they use speed... And we don't know if the caller wants to send functions or direction. 237 */ 238 @Override 239 protected void sendFunctionGroup1() { 240 int new_dirf = ((getIsForward() ? 0 : LnConstants.DIRF_DIR) 241 | (getFunction(0) ? LnConstants.DIRF_F0 : 0) 242 | (getFunction(1) ? LnConstants.DIRF_F1 : 0) 243 | (getFunction(2) ? LnConstants.DIRF_F2 : 0) 244 | (getFunction(3) ? LnConstants.DIRF_F3 : 0) 245 | (getFunction(4) ? LnConstants.DIRF_F4 : 0)); 246 log.debug("sendFunctionGroup1 sending {} to LocoNet slot {}", new_dirf, slot.getSlot()); 247 LocoNetMessage msg = new LocoNetMessage(4); 248 msg.setOpCode(LnConstants.OPC_LOCO_DIRF); 249 msg.setElement(1, slot.getSlot()); 250 msg.setElement(2, new_dirf); 251 network.sendLocoNetMessage(msg); 252 } 253 254 /** 255 * Send the LocoNet message to set the state of functions F5, F6, F7, F8 256 */ 257 @Override 258 protected void sendFunctionGroup2() { 259 int new_snd = ((getFunction(8) ? LnConstants.SND_F8 : 0) 260 | (getFunction(7) ? LnConstants.SND_F7 : 0) 261 | (getFunction(6) ? LnConstants.SND_F6 : 0) 262 | (getFunction(5) ? LnConstants.SND_F5 : 0)); 263 log.debug("sendFunctionGroup2 sending {} to LocoNet slot {}", new_snd, slot.getSlot()); 264 LocoNetMessage msg = new LocoNetMessage(4); 265 msg.setOpCode(LnConstants.OPC_LOCO_SND); 266 msg.setElement(1, slot.getSlot()); 267 msg.setElement(2, new_snd); 268 network.sendLocoNetMessage(msg); 269 } 270 271 /** 272 * Sends Function Group 3 values - F9 thru F12, using an "OPC_IMM_PACKET" LocoNet 273 * Message. 274 */ 275 @Override 276 protected void sendFunctionGroup3() { 277 // LocoNet practice is to send F9-F12 as a DCC packet 278 byte[] result = jmri.NmraPacket.function9Through12Packet(address, (address >= 128), 279 getFunction(9), getFunction(10), getFunction(11), getFunction(12)); 280 281 log.debug("sendFunctionGroup3 sending {} to LocoNet slot {}", result, slot.getSlot()); 282 adapterMemo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4 283 } 284 285 /** 286 * Sends Function Group 4 values - F13 thru F20, using an "OPC_IMM_PACKET" LocoNet 287 * Message. 288 */ 289 @Override 290 protected void sendFunctionGroup4() { 291 // LocoNet practice is to send F13-F20 as a DCC packet 292 byte[] result = jmri.NmraPacket.function13Through20Packet(address, (address >= 128), 293 getFunction(13), getFunction(14), getFunction(15), getFunction(16), 294 getFunction(17), getFunction(18), getFunction(19), getFunction(20)); 295 296 log.debug("sendFunctionGroup4 sending {} to LocoNet slot {}", result, slot.getSlot()); 297 adapterMemo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4 298 } 299 300 /** 301 * Sends Function Group 5 values - F21 thru F28, using an "OPC_IMM_PACKET" LocoNet 302 * Message. 303 */ 304 @Override 305 protected void sendFunctionGroup5() { 306 // LocoNet practice is to send F21-F28 as a DCC packet 307 byte[] result = jmri.NmraPacket.function21Through28Packet(address, (address >= 128), 308 getFunction(21), getFunction(22), getFunction(23), getFunction(24), 309 getFunction(25), getFunction(26), getFunction(27), getFunction(28)); 310 311 log.debug("sendFunctionGroup5 sending {} to LocoNet slot {}", result, slot.getSlot()); 312 adapterMemo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4 313 } 314 315 /** 316 * Sends Function Group 6 values - F29 thru F36, using an "OPC_IMM_PACKET" LocoNet 317 * Message. 318 */ 319 @Override 320 protected void sendFunctionGroup6() { 321 // LocoNet practice is to send as a DCC packet 322 int i = 29; 323 byte[] result = jmri.NmraPacket.function29Through36Packet(address, (address >= 128), 324 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 325 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 326 327 log.debug("sendFunctionGroup6 sending {} to LocoNet slot {}", result, slot.getSlot()); 328 adapterMemo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4 329 } 330 331 /** 332 * Sends Function Group 7 values - F37 thru F44, using an "OPC_IMM_PACKET" LocoNet 333 * Message. 334 */ 335 @Override 336 protected void sendFunctionGroup7() { 337 // LocoNet practice is to send as a DCC packet 338 int i = 37; 339 byte[] result = jmri.NmraPacket.function37Through44Packet(address, (address >= 128), 340 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 341 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 342 343 log.debug("sendFunctionGroup7 sending {} to LocoNet slot {}", result, slot.getSlot()); 344 adapterMemo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4 345 } 346 347 /** 348 * Sends Function Group 8 values - F45 thru F52, using an "OPC_IMM_PACKET" LocoNet 349 * Message. 350 */ 351 @Override 352 protected void sendFunctionGroup8() { 353 // LocoNet practice is to send as a DCC packet 354 int i = 45; 355 byte[] result = jmri.NmraPacket.function45Through52Packet(address, (address >= 128), 356 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 357 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 358 359 log.debug("sendFunctionGroup8 sending {} to LocoNet slot {}", result, slot.getSlot()); 360 adapterMemo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4 361 } 362 363 /** 364 * Sends Function Group 9 values - F53 thru F60, using an "OPC_IMM_PACKET" LocoNet 365 * Message. 366 */ 367 @Override 368 protected void sendFunctionGroup9() { 369 // LocoNet practice is to send as a DCC packet 370 int i = 53; 371 byte[] result = jmri.NmraPacket.function53Through60Packet(address, (address >= 128), 372 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 373 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 374 375 log.debug("sendFunctionGroup9 sending {} to LocoNet slot {}", result, slot.getSlot()); 376 adapterMemo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4 377 } 378 379 /** 380 * Sends Function Group 10 values - F61 thru F68, using an "OPC_IMM_PACKET" LocoNet 381 * Message. 382 */ 383 @Override 384 protected void sendFunctionGroup10() { 385 // LocoNet practice is to send as a DCC packet 386 int i = 61; 387 byte[] result = jmri.NmraPacket.function61Through68Packet(address, (address >= 128), 388 getFunction(i), getFunction(i+1), getFunction(i+2), getFunction(i+3), 389 getFunction(i+4), getFunction(i+5), getFunction(i+6), getFunction(i+7)); 390 391 log.debug("sendFunctionGroup10 sending {} to LocoNet slot {}", result, slot.getSlot()); 392 adapterMemo.get(jmri.CommandStation.class).sendPacket(result, 4); // repeat = 4 393 } 394 395 /** 396 * Send the Expanded LocoNet message to set the state of locomotive direction and 397 * functions F0, F1, F2, F3, F4, F5, F6 398 */ 399 protected void sendExpFunctionGroup1() { 400 int new_F0F6 = ((getFunction(5) ? 0b00100000 : 0) | (getFunction(6) ? 0b01000000 : 0) 401 | (getFunction(0) ? LnConstants.DIRF_F0 : 0) 402 | (getFunction(1) ? LnConstants.DIRF_F1 : 0) 403 | (getFunction(2) ? LnConstants.DIRF_F2 : 0) 404 | (getFunction(3) ? LnConstants.DIRF_F3 : 0) 405 | (getFunction(4) ? LnConstants.DIRF_F4 : 0)); 406 LocoNetMessage msg = new LocoNetMessage(6); 407 msg.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR); 408 msg.setElement(1, (slot.getSlot() / 128) | LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F0F6 ); 409 msg.setElement(2,slot.getSlot() & 0b01111111); 410 msg.setElement(3,slot.id() & 0x7F); 411 msg.setElement(4, new_F0F6); 412 network.sendLocoNetMessage(msg); 413 } 414 415 /** 416 * Send the Expanded LocoNet message to set the state of functions F7, F8, F8, F9, F10, F11, F12, F13 417 */ 418 protected void sendExpFunctionGroup2() { 419 int new_F7F13 = ((getFunction(7) ? 0b00000001 : 0) | (getFunction(8) ? 0b00000010 : 0) 420 | (getFunction(9) ? 0b00000100 : 0) 421 | (getFunction(10) ? 0b00001000 : 0) 422 | (getFunction(11) ? 0b00010000 : 0) 423 | (getFunction(12) ? 0b00100000 : 0) 424 | (getFunction(13) ? 0b01000000 : 0)); 425 LocoNetMessage msg = new LocoNetMessage(6); 426 msg.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR); 427 msg.setElement(1, (slot.getSlot() / 128) | LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F7F13 ); 428 msg.setElement(2,slot.getSlot() & 0b01111111); 429 msg.setElement(3,slot.id() & 0x7F); 430 msg.setElement(4, new_F7F13); 431 network.sendLocoNetMessage(msg); 432 } 433 434 /** 435 * Sends expanded loconet message F14 thru F20 436 * Message. 437 */ 438 protected void sendExpFunctionGroup3() { 439 int new_F14F20 = ((getFunction(14) ? 0b00000001 : 0) | (getFunction(15) ? 0b00000010 : 0) 440 | (getFunction(16) ? 0b00000100 : 0) 441 | (getFunction(17) ? 0b00001000 : 0) 442 | (getFunction(18) ? 0b00010000 : 0) 443 | (getFunction(19) ? 0b00100000 : 0) 444 | (getFunction(20) ? 0b01000000 : 0)); 445 LocoNetMessage msg = new LocoNetMessage(6); 446 msg.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR); 447 msg.setElement(1, (slot.getSlot() / 128) | LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F14F20 ); 448 msg.setElement(2,slot.getSlot() & 0b01111111); 449 msg.setElement(3,slot.id() & 0x7F); 450 msg.setElement(4, new_F14F20); 451 network.sendLocoNetMessage(msg); 452 } 453 454 /** 455 * Sends Expanded loconet message F21 thru F28 Message. 456 */ 457 protected void sendExpFunctionGroup4() { 458 int new_F2128 = ((getFunction(21) ? 0b00000001 : 0) | 459 (getFunction(22) ? 0b00000010 : 0) | 460 (getFunction(23) ? 0b00000100 : 0) | 461 (getFunction(24) ? 0b00001000 : 0) | 462 (getFunction(25) ? 0b00010000 : 0) | 463 (getFunction(26) ? 0b00100000 : 0) | 464 (getFunction(27) ? 0b01000000 : 0)); 465 LocoNetMessage msg = new LocoNetMessage(6); 466 msg.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR); 467 if (!getFunction(28)) { 468 msg.setElement(1, (slot.getSlot() / 128) | LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28OFF); 469 } else { 470 msg.setElement(1, (slot.getSlot() / 128) | LnConstants.OPC_EXP_SEND_FUNCTION_GROUP_F21F28_F28ON); 471 } 472 msg.setElement(2, slot.getSlot() & 0b01111111); 473 msg.setElement(3, slot.id() & 0x7F); 474 msg.setElement(4, new_F2128); 475 network.sendLocoNetMessage(msg); 476 } 477 478 /** 479 * Send the expanded slot command for speed and direction on change of speed 480 * Note we send our stored values as slot is updated via an echo 481 * and may not have been updated yet when sending rapid commands 482 * @param speed the speed to set 483 */ 484 protected void sendExpSpeedAndDirection(int speed) { 485 boolean isFwd; 486 if (slot.getLastUpdateTime() < new_isFwd_lastupdated) { 487 isFwd = new_isFwd; 488 } else { 489 isFwd = slot.isForward(); 490 } 491 // save last speed update for change of direction; 492 new_spd = speed; 493 new_spd_lastupdated = System.currentTimeMillis(); 494 LocoNetMessage msg = new LocoNetMessage(6); 495 msg.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR); 496 msg.setElement(1, ((slot.getSlot() / 128) & 0x03) | (isFwd ? 0x00 : 0x08)); 497 msg.setElement(2, slot.getSlot() & 0x7f); 498 msg.setElement(3, (slot.id() & 0x7f)); 499 msg.setElement(4, speed); 500 network.sendLocoNetMessage(msg); 501 } 502 503 /** 504 * Send the expanded slot command for speed and direction on change of direction 505 * Note we send our stored speed if slot has not yet been updated by the echo 506 * @param isFwd new direction 507 */ 508 protected void sendExpSpeedAndDirection(boolean isFwd) { 509 int speed; 510 if (slot.getLastUpdateTime() < new_spd_lastupdated) { 511 speed = new_spd; 512 } else { 513 speed = slot.speed(); 514 } 515 // save last speed update for change of direction; 516 new_isFwd = isFwd; 517 new_isFwd_lastupdated = System.currentTimeMillis(); 518 LocoNetMessage msg = new LocoNetMessage(6); 519 msg.setOpCode(LnConstants.OPC_EXP_SEND_FUNCTION_OR_SPEED_AND_DIR); 520 msg.setElement(1, ((slot.getSlot() / 128) & 0x03) | (isFwd ? 0x00 : 0x08)); 521 msg.setElement(2, slot.getSlot() & 0x7f); 522 msg.setElement(3, (slot.id() & 0x7f)); 523 msg.setElement(4, speed); 524 network.sendLocoNetMessage(msg); 525 } 526 527 /** 528 * Send a LocoNet message to set the loco speed speed. 529 * 530 * @param speed Number from 0 to 1; less than zero is "emergency stop" 531 */ 532 @Override 533 public void setSpeedSetting(float speed) { 534 setSpeedSetting(speed, false, false); 535 } 536 537 /** 538 * Set the Speed, ensuring that a LocoNet message is sent to update the slot 539 * even if the new speed is effectively the same as the current speed. Note: this 540 * can cause an increase in LocoNet traffic. 541 * 542 * @param speed Number from 0 to 1; less than zero is emergency stop 543 */ 544 @Override 545 public void setSpeedSettingAgain(float speed) { 546 setSpeedSetting(speed, true, true); 547 } 548 549 /** 550 * Set the speed. No LocoNet message is sent if the new speed would 551 * result in a 'duplicate' - ie. a speed setting no different to the one the slot 552 * currently has - unless the boolean paramters indicate it should be. 553 * 554 * @param speed Number from 0 to 1; less than zero is emergency stop 555 * @param allowDuplicates boolean - if true, send a LocoNet message no matter what 556 * @param allowDuplicatesOnStop boolean - if true, send a LocoNet message if the new speed is 557 * 'idle' or 'emergency stop', even if that matches the 558 * existing speed. 559 * 560 */ 561 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change 562 @Override 563 public void setSpeedSetting(float speed, boolean allowDuplicates, boolean allowDuplicatesOnStop) { 564 log.debug("setSpeedSetting: called with speed {} for LocoNet slot {} allowDup {} allowDupOnStop {}", 565 speed, slot.getSlot(), allowDuplicates, allowDuplicatesOnStop); 566 if (LnConstants.CONSIST_MID == slot.consistStatus() 567 || LnConstants.CONSIST_SUB == slot.consistStatus()) { 568 // Digitrax slots use the same memory location to store the 569 // speed AND the slot to which a locomotive is consisted. 570 // if the locomotive is either a CONSIST_MID or a CONSIST_SUB, 571 // we need to ignore the request to change the speed 572 log.debug("Attempt to change speed on locomotive {} which is a {}", getLocoAddress(), LnConstants.CONSIST_STAT(slot.consistStatus())); 573 return; 574 } 575 float oldSpeed; 576 synchronized(this) { 577 oldSpeed = this.speedSetting; 578 this.speedSetting = speed; 579 if (speed < 0) { 580 this.speedSetting = -1.f; 581 } 582 } 583 584 new_spd = intSpeed(speed); 585 586 // decide whether to send a new LocoNet message 587 boolean sendLoconetMessage = false; 588 if (new_spd != layout_spd ) { 589 // the new speed is different - send a message 590 sendLoconetMessage = true; 591 } else if (allowDuplicates) { 592 // calling method wants a new message sent regardless 593 sendLoconetMessage = true; 594 } else if (allowDuplicatesOnStop && new_spd <= 1) { 595 // calling method wants a new message sent if the speed is idle or estop, which it is 596 sendLoconetMessage = true; 597 } 598 599 if (sendLoconetMessage) { 600 log.debug("setSpeedSetting: sending speed {} to LocoNet slot {}", speed, slot.getSlot()); 601 if (slot.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) { 602 LocoNetMessage msg = new LocoNetMessage(4); 603 msg.setOpCode(LnConstants.OPC_LOCO_SPD); 604 msg.setElement(1, slot.getSlot()); 605 log.debug("setSpeedSetting: float speed: {} LocoNet speed: {}", speed, new_spd); 606 msg.setElement(2, new_spd); 607 network.sendLocoNetMessage(msg); 608 } else { 609 sendExpSpeedAndDirection(new_spd); 610 } 611 612 // reset timeout - but only if something sent on net 613 if (mRefreshTimer != null) { 614 mRefreshTimer.stop(); 615 mRefreshTimer.setRepeats(true); // refresh until stopped by dispose 616 mRefreshTimer.start(); 617 log.debug("Initially starting refresh timer for slot {} address {}", slot.getSlot(), slot.locoAddr()); 618 } 619 } else { 620 log.debug("setSpeedSetting: not sending LocoNet speed message to slot {}, new({})==old({})", slot.getSlot(), new_spd, layout_spd); 621 } 622 synchronized(this) { 623 firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting); 624 } 625 log.debug("about to invoke record({})", speed); 626 record(speed); 627 } 628 629 /** 630 * Send a LocoNet message containing the specified direction of travel. 631 * 632 * LocoNet actually puts forward and backward in the same message as the 633 * first function group. 634 * 635 * @param forward is true for forward movement, else false 636 */ 637 @Override 638 public void setIsForward(boolean forward) { 639 boolean old = isForward; 640 isForward = forward; 641 log.debug("setIsForward to {}, old value {}", isForward, old); 642 if (slot.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) { 643 sendFunctionGroup1(); 644 } else { 645 sendExpSpeedAndDirection(forward); 646 } 647 firePropertyChange(ISFORWARD, old, this.isForward); 648 } 649 650 /** 651 * Get the LocoNetSlot which is used for controlling the loco assoicated 652 * with this throttle. 653 * 654 * @return the LocoNetSlot 655 */ 656 @CheckForNull 657 public LocoNetSlot getLocoNetSlot() { 658 if (slot == null) return slot; 659 log.debug("getLocoNetSlot is returning slot {}", slot.getSlot()); 660 return slot; 661 } 662 663 @Override 664 public String toString() { 665 return getLocoAddress().toString(); 666 } 667 668 /** 669 * Dispose the LocoNetThrottle when finished with this object. 670 * 671 * After this is executed, further use of this Throttle object will 672 * result in a JmriException. 673 */ 674 @Override 675 public void throttleDispose() { 676 if (isDisposing) return; 677 log.debug("throttleDispose - disposing of throttle (and setting slot = null)"); 678 isDisposing = true; 679 680 // Release throttle connections 681 if (slot != null) { 682 if (slot.slotStatus() == LnConstants.LOCO_IN_USE ) { 683 // Digitrax throttles do not set the slot speed to zero, so do 684 // not do so here. 685 686 // Make the slot common, after a little wait 687 log.debug("dispatchThrottle is dispatching slot {}", slot); 688 network.sendLocoNetMessage(slot.releaseSlot()); 689 } 690 // Can remove the slot listener at any time; any further messages 691 // aren't needed. 692 slot.removeSlotListener(this); 693 // Stop the throttle speed refresh timer 694 if (mRefreshTimer != null) { 695 mRefreshTimer.stop(); 696 log.debug("Stopped refresh timer for slot {} address {} as part of throttleDispose", slot.getSlot(), slot.locoAddr()); 697 mRefreshTimer = null; 698 } 699 700 slot = null; 701 network = null; 702 703 finishRecord(); 704 isDisposing = false; 705 } 706 } 707 708 javax.swing.Timer mRefreshTimer = null; 709 710 /** 711 * Start the "refresh" timer. The "refresh" timer determines 712 * when to send a new LocoNet message to "refresh" the slot's speed 713 * setting, so that the slot does not get "purged". 714 * 715 */ 716 protected void startRefresh() { 717 mRefreshTimer = new javax.swing.Timer(50000, e -> timeout()); 718 mRefreshTimer.setRepeats(true); // refresh until stopped by dispose 719 mRefreshTimer.start(); 720 log.debug("Starting refresh timer for slot {} address {}", slot.getSlot(), slot.locoAddr()); 721 } 722 723 /** 724 * Internal routine to resend the speed on a timeout 725 */ 726 protected synchronized void timeout() { 727 if (slot != null) { 728 log.debug("refresh timer timed-out on slot {}", slot.getSlot()); 729 // clear the last known layout_spd so that we will actually send the 730 // message. 731 layout_spd = -1; 732 setSpeedSetting(speedSetting); 733 } 734 else { 735 log.debug("refresh timer time-out on a null slot"); 736 } 737 } 738 739 /** 740 * Get notified when underlying slot acquisition process fails. Slot acquisition 741 * failure is handled by @link LnThrottleManager, so no code is required here. 742 * 743 * @param addr Locomotive address 744 * @param s reason the acquisition failed 745 */ 746 public void notifyRefused(int addr, String s) { 747 // don't do anything here; is handled by LnThrottleManager. 748 } 749 750 751 /** 752 * Get notified when underlying slot information changes 753 * 754 * @param pSlot the slot which was changed 755 */ 756 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change 757 @Override 758 public void notifyChangedSlot(LocoNetSlot pSlot) { 759 log.debug("notifyChangedSlot executing for slot {}, slotStatus {}", slot.getSlot(), Integer.toHexString(slot.slotStatus())); 760 if (slot != pSlot) { 761 log.error("notified of change in different slot"); 762 } 763 764 if(!slot.getIsInitilized() && slot.slotStatus() == LnConstants.LOCO_IN_USE){ 765 log.debug("Attempting to update slot with this JMRI instance's throttle id ({})", throttleManager.getThrottleID()); 766 network.sendLocoNetMessage(slot.writeThrottleID(throttleManager.getThrottleID())); 767 // finally we are done... 768 slot.setIsInitialized(true); 769 throttleManager.notifyComplete(this, slot); 770 } 771 772 // Save current layout state of spd/dirf/snd so we won't run amok 773 // toggling values if another LocoNet entity accesses the slot while 774 // our most recent change request is still in-flight. 775 layout_spd = slot.speed(); 776 layout_dirf = slot.dirf(); 777 layout_snd = slot.snd(); 778 779 // handle change in each state 780 synchronized(this) { 781 if (this.speedSetting != floatSpeed(slot.speed())) { 782 float old = this.speedSetting; 783 this.speedSetting = floatSpeed(slot.speed()); 784 log.debug("notifyChangedSlot: old speed: {} new speed: {}", old, this.speedSetting); // NOI18N 785 firePropertyChange(SPEEDSETTING, old, this.speedSetting); 786 } 787 } 788 firePropertyChange(ISFORWARD, this.isForward, this.isForward = slot.isForward()); 789 790 // Slot status 791 if (slotStatus != slot.slotStatus()) { 792 int newStat = slot.slotStatus(); 793 log.debug("Slot status changed from {} to {}", LnConstants.LOCO_STAT(slotStatus), LnConstants.LOCO_STAT(newStat)); // NOI18N 794 // PropertyChangeListeners notification: ThrottleConnected from True to False when disconnected 795 firePropertyChange("ThrottleConnected", (slotStatus & LnConstants.LOCOSTAT_MASK) == LnConstants.LOCO_IN_USE, // NOI18N 796 !((slotStatus & LnConstants.LOCOSTAT_MASK) == LnConstants.LOCO_IN_USE)); 797 slotStatus = newStat; 798 } 799 800 // It is possible that the slot status change we are being notified of 801 // is the slot being set to status COMMON. In which case the slot just 802 // got set to null. No point in continuing. In fact to do so causes a NPE. 803 if (slot == null) { 804 return; 805 } 806 807 switch (slot.decoderType()) { 808 case LnConstants.DEC_MODE_128: 809 case LnConstants.DEC_MODE_128A: 810 if(SpeedStepMode.NMRA_DCC_128 != getSpeedStepMode()) { 811 setSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 812 } 813 break; 814 case LnConstants.DEC_MODE_28: 815 case LnConstants.DEC_MODE_28A: 816 case LnConstants.DEC_MODE_28TRI: 817 if(SpeedStepMode.NMRA_DCC_28 != getSpeedStepMode()) { 818 setSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 819 } 820 break; 821 case LnConstants.DEC_MODE_14: 822 if(SpeedStepMode.NMRA_DCC_14 != getSpeedStepMode()) { 823 setSpeedStepMode(SpeedStepMode.NMRA_DCC_14); 824 } 825 break; 826 default: 827 log.warn("Unhandled decoder type: {}", slot.decoderType()); 828 break; 829 } 830 831 // Functions 832 updateFunctions(); 833 834 log.debug("notifyChangedSlot ends"); 835 } 836 837 /** 838 * update the F0-F29 functions. 839 * Invoked by notifyChangedSlot(), this nominally updates from the slot. 840 */ 841 protected void updateFunctions() { 842 for (int i = 0; i < 29; i++) { 843 log.debug("updateFunction({}, {})", i, slot.isFunction(i)); 844 if (i==20 && log.isTraceEnabled()) log.trace("Tracing back F20", new Exception("traceback")); 845 updateFunction(i,slot.isFunction(i)); 846 } 847 } 848 849 /** 850 * Set the speed step value and the related speedIncrement value. 851 * 852 * @param Mode the current speed step mode - default should be 128 853 * speed step mode in most cases 854 */ 855 @Override 856 public void setSpeedStepMode(SpeedStepMode Mode) { 857 int status = slot.slotStatus(); 858 log.debug("Speed Step Mode Change to Mode: {} Current mode is: {}", Mode, this.speedStepMode); // NOI18N 859 log.debug("Current Slot Mode: {}", LnConstants.DEC_MODE(status)); // NOI18N 860 firePropertyChange(SPEEDSTEPS, this.speedStepMode, this.speedStepMode = Mode); 861 if (Mode == SpeedStepMode.NMRA_DCC_14) { 862 log.debug("14 speed step change"); // NOI18N 863 status = status & ((~LnConstants.DEC_MODE_MASK) 864 | LnConstants.STAT1_SL_SPDEX) 865 | LnConstants.DEC_MODE_14; 866 } else if (Mode == SpeedStepMode.MOTOROLA_28) { 867 log.debug("28-Tristate speed step change"); 868 status = status & ((~LnConstants.DEC_MODE_MASK) 869 | LnConstants.STAT1_SL_SPDEX) 870 | LnConstants.DEC_MODE_28TRI; 871 } else if (Mode == SpeedStepMode.NMRA_DCC_28) { 872 log.debug("28 speed step change"); 873 status = status & ((~LnConstants.DEC_MODE_MASK) 874 | LnConstants.STAT1_SL_SPDEX); 875 // | LnConstants.DEC_MODE_28; // DEC_MODE_28 has a zero value, here for documentation 876 // it unfortunately shows a INT_VACUOUS_BIT_OPERATION in SpotBugs 877 // and I don't want to annote that around this entire long method 878 } else { // default to 128 speed step mode 879 log.debug("128 speed step change"); 880 status = status & ((~LnConstants.DEC_MODE_MASK) 881 | LnConstants.STAT1_SL_SPDEX) 882 | LnConstants.DEC_MODE_128; 883 } 884 log.debug("New Slot Mode: {}", LnConstants.DEC_MODE(status)); 885 if (slot.getIsInitilized() ) 886 // check that the throttle is completely initialized. 887 { 888 network.sendLocoNetMessage(slot.writeMode(status)); 889 } 890 } 891 892 /** 893 * Get the address controlled by this throttle. If the throttle is controlling. 894 * 895 * @return a LocoAddress for the address controlled by this throttle 896 */ 897 @Override 898 public LocoAddress getLocoAddress() { 899 if (slot != null) { 900 if ((slot.slotStatus() == LnConstants.LOCO_IN_USE) || 901 (slot.slotStatus() == LnConstants.LOCO_COMMON)) { 902 log.debug("getLocoAddress replying address {} for slot {}", address, slot.getSlot()); 903 return new DccLocoAddress(address, LnThrottleManager.isLongAddress(address)); 904 } 905 } 906 log.debug("getLocoAddress replying address {} for slot not in-use or for sub-consisted slot or for null slot", address); 907 return new DccLocoAddress(address, LnThrottleManager.isLongAddress(address)); 908 } 909 910 /** 911 * "Dispatch" a LocoNet throttle by setting the slot as "common" then performing 912 * a slot move to slot 0. 913 * <p> 914 * The throttle being dispatched no longer has control of the loco, but other 915 * throttles may continue to control the loco. 916 * 917 * @param t throttle being dispatched 918 * @param l throttle listener to remove 919 */ 920 public void dispatchThrottle(DccThrottle t, ThrottleListener l) { 921 log.debug("dispatchThrottle - throttle {}", t.getLocoAddress()); 922 // set status to common & dispatch slot 923 // needs to be done one after another with no delay. 924 if (t instanceof LocoNetThrottle){ 925 LocoNetThrottle lnt = (LocoNetThrottle) t; 926 LocoNetSlot tSlot = lnt.getLocoNetSlot(); 927 if (tSlot != null) { 928 if (tSlot.slotStatus() != LnConstants.LOCO_COMMON) { 929 network.sendLocoNetMessage(tSlot.writeStatus(LnConstants.LOCO_COMMON)); 930 log.debug("dispatchThrottle is dispatching slot {}", tSlot); 931 network.sendLocoNetMessage(tSlot.dispatchSlot()); 932 } 933 } 934 } 935 } 936 937 // initialize logging 938 private final static Logger log = LoggerFactory.getLogger(LocoNetThrottle.class); 939 940}