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 @Override 574 public void setSpeedSetting(float speed, boolean allowDuplicates, boolean allowDuplicatesOnStop) { 575 log.debug("setSpeedSetting: called with speed {} for LocoNet slot {} allowDup {} allowDupOnStop {}", 576 speed, slot.getSlot(), allowDuplicates, allowDuplicatesOnStop); 577 if (LnConstants.CONSIST_MID == slot.consistStatus() 578 || LnConstants.CONSIST_SUB == slot.consistStatus()) { 579 // Digitrax slots use the same memory location to store the 580 // speed AND the slot to which a locomotive is consisted. 581 // if the locomotive is either a CONSIST_MID or a CONSIST_SUB, 582 // we need to ignore the request to change the speed 583 log.debug("Attempt to change speed on locomotive {} which is a {}", getLocoAddress(), LnConstants.CONSIST_STAT(slot.consistStatus())); 584 return; 585 } 586 float oldSpeed; 587 synchronized(this) { 588 oldSpeed = this.speedSetting; 589 this.speedSetting = speed; 590 if (speed < 0) { 591 this.speedSetting = -1.f; 592 } 593 } 594 595 new_spd = intSpeed(speed); 596 597 // decide whether to send a new LocoNet message 598 boolean sendLoconetMessage = false; 599 if (new_spd != layout_spd ) { 600 // the new speed is different - send a message 601 sendLoconetMessage = true; 602 } else if (allowDuplicates) { 603 // calling method wants a new message sent regardless 604 sendLoconetMessage = true; 605 } else if (allowDuplicatesOnStop && new_spd <= 1) { 606 // calling method wants a new message sent if the speed is idle or estop, which it is 607 sendLoconetMessage = true; 608 } 609 610 if (sendLoconetMessage) { 611 log.debug("setSpeedSetting: sending speed {} to LocoNet slot {}", speed, slot.getSlot()); 612 if (slot.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) { 613 LocoNetMessage msg = new LocoNetMessage(4); 614 msg.setOpCode(LnConstants.OPC_LOCO_SPD); 615 msg.setElement(1, slot.getSlot()); 616 log.debug("setSpeedSetting: float speed: {} LocoNet speed: {}", speed, new_spd); 617 msg.setElement(2, new_spd); 618 network.sendLocoNetMessage(msg); 619 } else { 620 sendExpSpeedAndDirection(new_spd); 621 } 622 623 // reset timeout - but only if something sent on net 624 if (mRefreshTimer != null) { 625 mRefreshTimer.stop(); 626 mRefreshTimer.setRepeats(true); // refresh until stopped by dispose 627 mRefreshTimer.start(); 628 log.debug("Initially starting refresh timer for slot {} address {}", slot.getSlot(), slot.locoAddr()); 629 } 630 } else { 631 log.debug("setSpeedSetting: not sending LocoNet speed message to slot {}, new({})==old({})", slot.getSlot(), new_spd, layout_spd); 632 } 633 synchronized(this) { 634 firePropertyChange(SPEEDSETTING, oldSpeed, this.speedSetting); 635 } 636 log.debug("about to invoke record({})", speed); 637 record(speed); 638 } 639 640 /** 641 * Send a LocoNet message containing the specified direction of travel. 642 * 643 * LocoNet actually puts forward and backward in the same message as the 644 * first function group. 645 * 646 * @param forward is true for forward movement, else false 647 */ 648 @Override 649 public void setIsForward(boolean forward) { 650 boolean old = isForward; 651 isForward = forward; 652 log.debug("setIsForward to {}, old value {}", isForward, old); 653 if (slot.getProtocol() != LnConstants.LOCONETPROTOCOL_TWO) { 654 sendFunctionGroup1(); 655 } else { 656 sendExpSpeedAndDirection(forward); 657 } 658 firePropertyChange(ISFORWARD, old, this.isForward); 659 } 660 661 /** 662 * Get the LocoNetSlot which is used for controlling the loco assoicated 663 * with this throttle. 664 * 665 * @return the LocoNetSlot 666 */ 667 @CheckForNull 668 public LocoNetSlot getLocoNetSlot() { 669 if (slot == null) return slot; 670 log.debug("getLocoNetSlot is returning slot {}", slot.getSlot()); 671 return slot; 672 } 673 674 @Override 675 public String toString() { 676 return getLocoAddress().toString(); 677 } 678 679 /** 680 * Dispose the LocoNetThrottle when finished with this object. 681 * 682 * After this is executed, further use of this Throttle object will 683 * result in a JmriException. 684 */ 685 @Override 686 public void throttleDispose() { 687 if (isDisposing) return; 688 log.debug("throttleDispose - disposing of throttle (and setting slot = null)"); 689 isDisposing = true; 690 691 // Release throttle connections 692 if (slot != null) { 693 if (slot.slotStatus() == LnConstants.LOCO_IN_USE ) { 694 // Digitrax throttles do not set the slot speed to zero, so do 695 // not do so here. 696 697 // Make the slot common, after a little wait 698 log.debug("dispatchThrottle is dispatching slot {}", slot); 699 network.sendLocoNetMessage(slot.releaseSlot()); 700 } 701 // Can remove the slot listener at any time; any further messages 702 // aren't needed. 703 slot.removeSlotListener(this); 704 // Stop the throttle speed refresh timer 705 if (mRefreshTimer != null) { 706 mRefreshTimer.stop(); 707 log.debug("Stopped refresh timer for slot {} address {} as part of throttleDispose", slot.getSlot(), slot.locoAddr()); 708 mRefreshTimer = null; 709 } 710 711 slot = null; 712 network = null; 713 714 finishRecord(); 715 isDisposing = false; 716 } 717 } 718 719 javax.swing.Timer mRefreshTimer = null; 720 721 /** 722 * Start the "refresh" timer. The "refresh" timer determines 723 * when to send a new LocoNet message to "refresh" the slot's speed 724 * setting, so that the slot does not get "purged". 725 * 726 */ 727 protected void startRefresh() { 728 mRefreshTimer = new javax.swing.Timer(50000, e -> timeout()); 729 mRefreshTimer.setRepeats(true); // refresh until stopped by dispose 730 mRefreshTimer.start(); 731 log.debug("Starting refresh timer for slot {} address {}", slot.getSlot(), slot.locoAddr()); 732 } 733 734 /** 735 * Internal routine to resend the speed on a timeout 736 */ 737 protected synchronized void timeout() { 738 if (slot != null) { 739 log.debug("refresh timer timed-out on slot {}", slot.getSlot()); 740 // clear the last known layout_spd so that we will actually send the 741 // message. 742 layout_spd = -1; 743 setSpeedSetting(speedSetting); 744 } 745 else { 746 log.debug("refresh timer time-out on a null slot"); 747 } 748 } 749 750 /** 751 * Get notified when underlying slot acquisition process fails. Slot acquisition 752 * failure is handled by @link LnThrottleManager, so no code is required here. 753 * 754 * @param addr Locomotive address 755 * @param s reason the acquisition failed 756 */ 757 public void notifyRefused(int addr, String s) { 758 // don't do anything here; is handled by LnThrottleManager. 759 } 760 761 762 /** 763 * Get notified when underlying slot information changes 764 * 765 * @param pSlot the slot which was changed 766 */ 767 @SuppressFBWarnings(value = "FE_FLOATING_POINT_EQUALITY") // OK to compare floating point, notify on any change 768 @Override 769 public void notifyChangedSlot(LocoNetSlot pSlot) { 770 log.debug("notifyChangedSlot executing for slot {}, slotStatus {}", slot.getSlot(), Integer.toHexString(slot.slotStatus())); 771 if (slot != pSlot) { 772 log.error("notified of change in different slot"); 773 } 774 775 if(!slot.getIsInitilized() && slot.slotStatus() == LnConstants.LOCO_IN_USE){ 776 log.debug("Attempting to update slot with this JMRI instance's throttle id ({})", throttleManager.getThrottleID()); 777 network.sendLocoNetMessage(slot.writeThrottleID(throttleManager.getThrottleID())); 778 // finally we are done... 779 slot.setIsInitialized(true); 780 throttleManager.notifyComplete(this, slot); 781 } 782 783 // Save current layout state of spd/dirf/snd so we won't run amok 784 // toggling values if another LocoNet entity accesses the slot while 785 // our most recent change request is still in-flight. 786 layout_spd = slot.speed(); 787 layout_dirf = slot.dirf(); 788 layout_snd = slot.snd(); 789 790 // handle change in each state 791 synchronized(this) { 792 if (this.speedSetting != floatSpeed(slot.speed())) { 793 float old = this.speedSetting; 794 this.speedSetting = floatSpeed(slot.speed()); 795 log.debug("notifyChangedSlot: old speed: {} new speed: {}", old, this.speedSetting); // NOI18N 796 firePropertyChange(SPEEDSETTING, old, this.speedSetting); 797 } 798 } 799 firePropertyChange(ISFORWARD, this.isForward, this.isForward = slot.isForward()); 800 801 // Slot status 802 if (slotStatus != slot.slotStatus()) { 803 int newStat = slot.slotStatus(); 804 log.debug("Slot status changed from {} to {}", LnConstants.LOCO_STAT(slotStatus), LnConstants.LOCO_STAT(newStat)); // NOI18N 805 // PropertyChangeListeners notification: ThrottleConnected from True to False when disconnected 806 firePropertyChange("ThrottleConnected", (slotStatus & LnConstants.LOCOSTAT_MASK) == LnConstants.LOCO_IN_USE, // NOI18N 807 !((slotStatus & LnConstants.LOCOSTAT_MASK) == LnConstants.LOCO_IN_USE)); 808 slotStatus = newStat; 809 } 810 811 // It is possible that the slot status change we are being notified of 812 // is the slot being set to status COMMON. In which case the slot just 813 // got set to null. No point in continuing. In fact to do so causes a NPE. 814 if (slot == null) { 815 return; 816 } 817 818 switch (slot.decoderType()) { 819 case LnConstants.DEC_MODE_128: 820 case LnConstants.DEC_MODE_128A: 821 if(SpeedStepMode.NMRA_DCC_128 != getSpeedStepMode()) { 822 setSpeedStepMode(SpeedStepMode.NMRA_DCC_128); 823 } 824 break; 825 case LnConstants.DEC_MODE_28: 826 case LnConstants.DEC_MODE_28A: 827 case LnConstants.DEC_MODE_28TRI: 828 if(SpeedStepMode.NMRA_DCC_28 != getSpeedStepMode()) { 829 setSpeedStepMode(SpeedStepMode.NMRA_DCC_28); 830 } 831 break; 832 case LnConstants.DEC_MODE_14: 833 if(SpeedStepMode.NMRA_DCC_14 != getSpeedStepMode()) { 834 setSpeedStepMode(SpeedStepMode.NMRA_DCC_14); 835 } 836 break; 837 default: 838 log.warn("Unhandled decoder type: {}", slot.decoderType()); 839 break; 840 } 841 842 // Functions 843 updateFunctions(); 844 845 log.debug("notifyChangedSlot ends"); 846 } 847 848 /** 849 * update the F0-F29 functions. 850 * Invoked by notifyChangedSlot(), this nominally updates from the slot. 851 */ 852 protected void updateFunctions() { 853 for (int i = 0; i < 29; i++) { 854 log.debug("updateFunction({}, {})", i, slot.isFunction(i)); 855 if (i==20 && log.isTraceEnabled()) log.trace("Tracing back F20", new Exception("traceback")); 856 updateFunction(i,slot.isFunction(i)); 857 } 858 } 859 860 /** 861 * Set the speed step value and the related speedIncrement value. 862 * 863 * @param Mode the current speed step mode - default should be 128 864 * speed step mode in most cases 865 */ 866 @Override 867 public void setSpeedStepMode(SpeedStepMode Mode) { 868 int status = slot.slotStatus(); 869 log.debug("Speed Step Mode Change to Mode: {} Current mode is: {}", Mode, this.speedStepMode); // NOI18N 870 log.debug("Current Slot Mode: {}", LnConstants.DEC_MODE(status)); // NOI18N 871 firePropertyChange(SPEEDSTEPS, this.speedStepMode, this.speedStepMode = Mode); 872 if (Mode == SpeedStepMode.NMRA_DCC_14) { 873 log.debug("14 speed step change"); // NOI18N 874 status = status & ((~LnConstants.DEC_MODE_MASK) 875 | LnConstants.STAT1_SL_SPDEX) 876 | LnConstants.DEC_MODE_14; 877 } else if (Mode == SpeedStepMode.MOTOROLA_28) { 878 log.debug("28-Tristate speed step change"); 879 status = status & ((~LnConstants.DEC_MODE_MASK) 880 | LnConstants.STAT1_SL_SPDEX) 881 | LnConstants.DEC_MODE_28TRI; 882 } else if (Mode == SpeedStepMode.NMRA_DCC_28) { 883 log.debug("28 speed step change"); 884 status = status & ((~LnConstants.DEC_MODE_MASK) 885 | LnConstants.STAT1_SL_SPDEX); 886 // | LnConstants.DEC_MODE_28; // DEC_MODE_28 has a zero value, here for documentation 887 // it unfortunately shows a INT_VACUOUS_BIT_OPERATION in SpotBugs 888 // and I don't want to annote that around this entire long method 889 } else { // default to 128 speed step mode 890 log.debug("128 speed step change"); 891 status = status & ((~LnConstants.DEC_MODE_MASK) 892 | LnConstants.STAT1_SL_SPDEX) 893 | LnConstants.DEC_MODE_128; 894 } 895 log.debug("New Slot Mode: {}", LnConstants.DEC_MODE(status)); 896 if (slot.getIsInitilized() ) 897 // check that the throttle is completely initialized. 898 { 899 network.sendLocoNetMessage(slot.writeMode(status)); 900 } 901 } 902 903 /** 904 * Get the address controlled by this throttle. If the throttle is controlling. 905 * 906 * @return a LocoAddress for the address controlled by this throttle 907 */ 908 @Override 909 public LocoAddress getLocoAddress() { 910 if (slot != null) { 911 if ((slot.slotStatus() == LnConstants.LOCO_IN_USE) || 912 (slot.slotStatus() == LnConstants.LOCO_COMMON)) { 913 log.debug("getLocoAddress replying address {} for slot {}", address, slot.getSlot()); 914 return new DccLocoAddress(address, LnThrottleManager.isLongAddress(address)); 915 } 916 } 917 log.debug("getLocoAddress replying address {} for slot not in-use or for sub-consisted slot or for null slot", address); 918 return new DccLocoAddress(address, LnThrottleManager.isLongAddress(address)); 919 } 920 921 /** 922 * "Dispatch" a LocoNet throttle by setting the slot as "common" then performing 923 * a slot move to slot 0. 924 * <p> 925 * The throttle being dispatched no longer has control of the loco, but other 926 * throttles may continue to control the loco. 927 * 928 * @param t throttle being dispatched 929 * @param l throttle listener to remove 930 */ 931 public void dispatchThrottle(DccThrottle t, ThrottleListener l) { 932 log.debug("dispatchThrottle - throttle {}", t.getLocoAddress()); 933 // set status to common & dispatch slot 934 // needs to be done one after another with no delay. 935 if (t instanceof LocoNetThrottle){ 936 LocoNetThrottle lnt = (LocoNetThrottle) t; 937 LocoNetSlot tSlot = lnt.getLocoNetSlot(); 938 if (tSlot != null) { 939 if (tSlot.slotStatus() != LnConstants.LOCO_COMMON) { 940 network.sendLocoNetMessage(tSlot.writeStatus(LnConstants.LOCO_COMMON)); 941 log.debug("dispatchThrottle is dispatching slot {}", tSlot); 942 network.sendLocoNetMessage(tSlot.dispatchSlot()); 943 } 944 } 945 } 946 } 947 948 // initialize logging 949 private final static Logger log = LoggerFactory.getLogger(LocoNetThrottle.class); 950 951}