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