001package jmri.jmrix.dccpp; 002 003import java.util.concurrent.LinkedBlockingQueue; 004import jmri.DccLocoAddress; 005import jmri.LocoAddress; 006import jmri.SpeedStepMode; 007import jmri.jmrix.AbstractThrottle; 008 009/** 010 * An implementation of DccThrottle with code specific to a DCC-EX 011 * connection. 012 * 013 * @author Paul Bender (C) 2002-2010 014 * @author Giorgio Terdina (C) 2007 015 * @author Mark Underwood (C) 2015 016 * 017 * Based on XNetThrottle by Paul Bender and Giorgio Terdina 018 */ 019public class DCCppThrottle extends AbstractThrottle implements DCCppListener { 020 021 protected DCCppTrafficController tc; 022 023 // status of the throttle 024 protected static final int THROTTLEIDLE = 0; // Idle Throttle 025 protected static final int THROTTLESPEEDSENT = 2; // Sent speed/dir command to locomotive 026 protected static final int THROTTLEFUNCSENT = 4; // Sent a function command to locomotive. 027 private static final float SPEEDMULTIPLIER = 1.0f / 126.0f; //used to convert from integer speed to what JMRI expects 028 029 int requestState = THROTTLEIDLE; 030 031 protected int address; 032 033 /** 034 * Constructor. 035 * @param memo system connection. 036 * @param controller system connection traffic controller. 037 */ 038 public DCCppThrottle(DCCppSystemConnectionMemo memo, DCCppTrafficController controller) { 039 super(memo, 69); // supports up to F68 040 tc = controller; 041 requestList = new LinkedBlockingQueue<>(); 042 this.isForward = true; //loco should default to forward 043 log.debug("DCCppThrottle constructor"); 044 } 045 046 /** 047 * Constructor. 048 * @param memo system connection. 049 * @param address loco address to set on throttle 050 * @param controller system connection traffic controller. 051 */ 052 public DCCppThrottle(DCCppSystemConnectionMemo memo, LocoAddress address, DCCppTrafficController controller) { 053 super(memo, 69); // supports up to F68 054 tc = controller; 055 if (address instanceof DccLocoAddress) { 056 this.setDccAddress(address.getNumber()); 057 } 058 else { 059 log.error("LocoAddress {} is not a DccLocoAddress",address); 060 } 061 this.speedStepMode = SpeedStepMode.NMRA_DCC_128; 062 063 requestList = new LinkedBlockingQueue<RequestMessage>(); 064 this.isForward = true; //loco should default to forward 065 log.debug("DCCppThrottle constructor called for address {}", address); 066 } 067 068 /* 069 * Set the traffic controller used with this throttle 070 */ 071 public void setDCCppTrafficController(DCCppTrafficController controller) { 072 tc = controller; 073 } 074 075 /** 076 * Get the Register Number for this Throttle's assigned address 077 * @return register number currently 078 */ 079 int getRegisterNum() { 080 return (tc.getCommandStation().getRegisterNum(this.getDccAddress())); 081 } 082 083 /** 084 * {@inheritDoc} 085 */ 086 @Override 087 public void setFunction(int functionNum, boolean newState) { 088 if (tc.getCommandStation().isFunctionV4Supported()) { 089 //send the newer <F CAB FUNC STATE> message 090 DCCppMessage msg = DCCppMessage.makeFunctionV4Message(this.getDccAddress(), functionNum, newState); 091 queueMessage(msg, THROTTLEIDLE); 092 updateFunction(functionNum, newState); //update throttle and broadcast change 093 } else { 094 //or send the older <f ADDR BYTE1 (BYTE2)> message 095 super.setFunction(functionNum, newState); 096 } 097 } 098 099 100 /** 101 * Send the DCC-EX message to set the state of locomotive direction and 102 * functions F0, F1, F2, F3, F4 103 */ 104 @Override 105 protected void sendFunctionGroup1() { 106 log.debug("sendFunctionGroup1(): f0 {} f1 {} f2 {} f3 {} f4 {}", 107 getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4)); 108 DCCppMessage msg = DCCppMessage.makeFunctionGroup1OpsMsg(this.getDccAddress(), 109 getFunction(0), getFunction(1), getFunction(2), getFunction(3), getFunction(4)); 110 log.debug("sendFunctionGroup1(): Message: {}", msg); 111 // now, queue the message for sending to the command station 112 //queueMessage(msg, THROTTLEFUNCSENT); 113 queueMessage(msg, THROTTLEIDLE); 114 } 115 116 /** 117 * Send the DCC-EX message to set the state of functions F5, F6, F7, F8 118 */ 119 @Override 120 protected void sendFunctionGroup2() { 121 DCCppMessage msg = DCCppMessage.makeFunctionGroup2OpsMsg(this.getDccAddress(), 122 getFunction(5), getFunction(6), getFunction(7), getFunction(8)); 123 // now, queue the message for sending to the command station 124 //queueMessage(msg, THROTTLEFUNCSENT); 125 queueMessage(msg, THROTTLEIDLE); 126 } 127 128 /** 129 * Send the DCC-EX message to set the state of functions F9, F10, F11, 130 * F12 131 */ 132 @Override 133 protected void sendFunctionGroup3() { 134 DCCppMessage msg = DCCppMessage.makeFunctionGroup3OpsMsg(this.getDccAddress(), 135 getFunction(9), getFunction(10), getFunction(11), getFunction(12)); 136 // now, queue the message for sending to the command station 137 //queueMessage(msg, THROTTLEFUNCSENT); 138 queueMessage(msg, THROTTLEIDLE); 139 } 140 141 /** 142 * Send the DCC-EX message to set the state of functions F13, F14, F15, 143 * F16, F17, F18, F19, F20 144 */ 145 @Override 146 protected void sendFunctionGroup4() { 147 DCCppMessage msg = DCCppMessage.makeFunctionGroup4OpsMsg(this.getDccAddress(), 148 getFunction(13), getFunction(14), getFunction(15), getFunction(16), 149 getFunction(17), getFunction(18), getFunction(19), getFunction(20)); 150 // now, queue the message for sending to the command station 151 //queueMessage(msg, THROTTLEFUNCSENT); 152 queueMessage(msg, THROTTLEIDLE); 153 } 154 155 /** 156 * Send the DCC-EX message to set the state of functions F21, F22, F23, 157 * F24, F25, F26, F27, F28 158 */ 159 @Override 160 protected void sendFunctionGroup5() { 161 log.debug("sendFunctionGroup5(): f21 {} f22 {} f23 {} f24 {} f25 {} f26 {} f27 {} f28 {}", 162 getFunction(21), getFunction(22), getFunction(23), getFunction(24), 163 getFunction(25), getFunction(26), getFunction(27), getFunction(28)); 164 DCCppMessage msg = DCCppMessage.makeFunctionGroup5OpsMsg(this.getDccAddress(), 165 getFunction(21), getFunction(22), getFunction(23), getFunction(24), 166 getFunction(25), getFunction(26), getFunction(27), getFunction(28)); 167 log.debug("sendFunctionGroup5(): Message: '{}'", msg); 168 // now, queue the message for sending to the command station 169 //queueMessage(msg, THROTTLEFUNCSENT); 170 queueMessage(msg, THROTTLEIDLE); 171 } 172 173 /* 174 * setSpeedSetting - notify listeners and send the new speed to the 175 * command station. 176 */ 177 @Override 178 synchronized public void setSpeedSetting(float speed) { 179 if (log.isDebugEnabled()) { 180 log.debug("set Speed to: {} Current step mode is: {}", speed, this.speedStepMode); 181 } 182 super.setSpeedSetting(speed); 183 if (speed < 0) { 184 /* we're sending an emergency stop to this locomotive only */ 185 sendEmergencyStop(); 186 } else { 187 if (speed > 1) { 188 speed = (float) 1.0; 189 } 190 /* we're sending a speed to the locomotive */ 191 DCCppMessage msg; 192 //older version includes register 193 if (tc.getCommandStation().isThrottleRegisterRequired()) { 194 msg = DCCppMessage.makeSpeedAndDirectionMsg( 195 getRegisterNum(), 196 getDccAddress(), 197 speed, 198 this.isForward); 199 } else { 200 //newer version does not need register passed 201 msg = DCCppMessage.makeSpeedAndDirectionMsg( 202 getDccAddress(), 203 speed, 204 this.isForward); 205 } 206 // now, queue the message for sending to the command station 207 //queueMessage(msg, THROTTLESPEEDSENT); 208 queueMessage(msg, THROTTLEIDLE); 209 } 210 } 211 212 /* Since DCC-EX has a separate Opcode for emergency stop, 213 * We're setting this up as a separate protected function 214 */ 215 protected void sendEmergencyStop() { 216 /* Emergency stop sent */ 217 DCCppMessage msg; 218 if (tc.getCommandStation().isThrottleRegisterRequired()) { 219 msg = DCCppMessage.makeAddressedEmergencyStop(getRegisterNum(), getDccAddress(), isForward); 220 } else { 221 msg = DCCppMessage.makeAddressedEmergencyStop(getDccAddress(), isForward); 222 } 223 // now, queue the message for sending to the command station 224 //queueMessage(msg, THROTTLESPEEDSENT); 225 queueMessage(msg, THROTTLEIDLE); 226 } 227 228 /* Since there is only one "throttle" command to the DCC-EX base station, 229 * when we change the direction, we must also re-set the speed. 230 */ 231 @Override 232 public void setIsForward(boolean forward) { 233 super.setIsForward(forward); 234 synchronized(this) { 235 setSpeedSetting(this.speedSetting); 236 } 237 } 238 239 /* 240 * setSpeedStepMode - set the speed step value and the related 241 * speedIncrement value. 242 * 243 * @param Mode the current speed step mode - default should be 128 244 * speed step mode in most cases 245 * 246 * NOTE: DCC-EX only supports 128-step mode. So we ignore the speed 247 * setting, even though we store it. 248 */ 249 @Override 250 public void setSpeedStepMode(SpeedStepMode Mode) { 251 super.setSpeedStepMode(Mode); 252 } 253 254 /** 255 * Dispose when finished with this object. After this, further usage of this 256 * Throttle object will result in a JmriException. 257 * 258 * This is quite problematic, because a using object doesn't know when it's 259 * the last user. 260 */ 261 @Override 262 protected void throttleDispose() { 263 active = false; 264 finishRecord(); 265 } 266 267 public final int setDccAddress(int newaddress) { 268 address = newaddress; 269 return address; 270 } 271 272 public int getDccAddress() { 273 return address; 274 } 275 276 277 protected int getDccAddressHigh() { 278 return DCCppCommandStation.getDCCAddressHigh(this.address); 279 } 280 281 protected int getDccAddressLow() { 282 return DCCppCommandStation.getDCCAddressLow(this.address); 283 } 284 285 // Handle incoming messages for This throttle. 286 @Override 287 public void message(DCCppReply l) { 288 // First, we want to see if this throttle is waiting for a message 289 //or not. 290 if (log.isDebugEnabled()) { 291 log.trace("Throttle {} - received message '{}'", getDccAddress(), l); 292 } 293 if (requestState == THROTTLEIDLE) { 294 log.trace("Current throttle status is THROTTLEIDLE"); 295 // We haven't sent anything, but we might be told someone else 296 // has taken over this address 297 // For now, do nothing. 298 } else if ((requestState & THROTTLESPEEDSENT) == THROTTLESPEEDSENT) { 299 log.debug("Current throttle status is THROTTLESPEEDSENT"); 300 // This is a reply to a Throttle message, or to a Status message. 301 if (l.isThrottleReply()) { 302 // Update our state with the register's information. 303 handleThrottleReply(l); 304 } 305 // For a Throttle command ("t") we get back a Throttle Status. 306 307 log.debug("Last Command processed successfully."); 308 309 requestState = THROTTLEIDLE; 310 sendQueuedMessage(); 311 312 } 313 if ((requestState & THROTTLEFUNCSENT) == THROTTLEFUNCSENT) { 314 log.debug("Current throttle status is THROTTLEFUNCSENT. Ignoring Reply: '{}'", l); 315 } 316 requestState=THROTTLEIDLE; 317 sendQueuedMessage(); 318 } 319 320 //check for any changes needed based on incoming LocoState reply for this throttle 321 //then make those changes directly to the parent throttle to avoid a message loop 322 protected void handleLocoState(DCCppReply r) { 323 int locoId = r.getLocoIdInt(); 324 //insure this message belongs to this throttle (really shouldn't happen) 325 if (this.address != locoId) { 326 log.error("throttle {} incorrectly called for locoId {}", this.address, locoId); 327 return; 328 } 329 330 boolean newForward = r.getIsForward(); 331 float newSpeedSetting = r.getSpeedInt() * SPEEDMULTIPLIER; 332 String newFunctionsString = r.getFunctionsString(); 333 334 if (this.getIsForward() != newForward) { 335 log.debug("changing forward from {} to {} for {}", this.getIsForward(), newForward, locoId); 336 super.setIsForward(newForward); 337 } 338 if (Math.abs(this.getSpeedSetting() - newSpeedSetting) > 0.0001) { //avoid possible float precision errors 339 log.debug("changing speed from {} to {} for {}", this.getSpeedSetting(), newSpeedSetting, locoId); 340 super.setSpeedSetting(newSpeedSetting); 341 } 342 //check each function value for any changes, and update if so 343 for (int i = 0; i <= 28; i++) { 344 boolean newState = (newFunctionsString.charAt(i)=='1'); 345 if (this.getFunction(i) != newState) { 346 log.debug("changing F{} from {} to {} for {}", 347 i, this.getFunction(i), newState, locoId); 348 super.updateFunction(i,newState); 349 } 350 } 351 } 352 353 private void handleThrottleReply(DCCppReply l) { 354 int reg = l.getRegisterInt(); 355 int speed = l.getSpeedInt(); 356 int dir = l.getDirectionInt(); 357 358 // Check to see if register matches MY throttle. 359 // If so, update my values to match the returned values. 360 // Make (relatively) direct writes to the memories, so we don't 361 // cause looped throttle messages. 362 int regaddr = tc.getCommandStation().getRegisterAddress(reg); 363 if ((regaddr == DCCppConstants.REGISTER_UNALLOCATED) || 364 (regaddr != this.address)) { 365 // This register doesn't match anything. 366 // Or the assigned address doesn't match mine. 367 } else { 368 // The assigned address matches mine. Update my info 369 // to match the returned register info. 370 synchronized(this) { 371 if (speed < 0) { 372 this.speedSetting = 0.0f; 373 } else { 374 this.speedSetting = speed * SPEEDMULTIPLIER; 375 } 376 } 377 this.isForward = (dir == 1); 378 } 379 } 380 381 /** 382 * Does not listen for outgoing messages. 383 * {@inheritDoc } 384 */ 385 @Override 386 public void message(DCCppMessage l) { 387 } 388 389 /** 390 * Handle a timeout notification 391 * {@inheritDoc } 392 */ 393 @Override 394 public void notifyTimeout(DCCppMessage msg) { 395 log.debug("Notified of timeout on message '{}' , {} retries available.", msg, msg.getRetries()); 396 if (msg.getRetries() > 0) { 397 // If the message still has retries available, send it back to 398 // the traffic controller. 399 tc.sendDCCppMessage(msg, this); 400 } else { 401 // Try to send the next queued message, if one is available. 402 sendQueuedMessage(); 403 } 404 } 405 406 @Override 407 public LocoAddress getLocoAddress() { 408 return new DccLocoAddress(address, DCCppThrottleManager.isLongAddress(address)); 409 } 410 411 //A queue to hold outstanding messages 412 private final LinkedBlockingQueue<RequestMessage> requestList; 413 414 // function to send message from queue. 415 private synchronized void sendQueuedMessage() { 416 RequestMessage msg; 417 // check to see if the queue has a message in it, and if it does, 418 // remove the first message 419 if (!requestList.isEmpty()) { 420 log.trace("sending message to traffic controller"); 421 // if the queue is not empty, remove the first message 422 // from the queue, send the message, and set the state machine 423 // to the requeried state. 424 try { 425 msg = requestList.take(); 426 } catch (InterruptedException ie) { 427 Thread.currentThread().interrupt(); 428 return; // if there was an error, exit. 429 } 430 requestState = msg.getState(); 431 tc.sendDCCppMessage(msg.getMsg(), this); 432 } else { 433 log.trace("message queue empty"); 434 // if the queue is empty, set the state to idle. 435 requestState = THROTTLEIDLE; 436 } 437 } 438 439 //function to queue a message 440 private synchronized void queueMessage(DCCppMessage m, int s) { 441 log.trace("adding message '{}' to message queue", m); 442 // put the message in the queue 443 RequestMessage msg = new RequestMessage(m, s); 444 try { 445 requestList.put(msg); 446 } catch (InterruptedException ignore) { 447 Thread.currentThread().interrupt(); 448 } 449 // if the state is idle, trigger the message send 450 if (requestState == THROTTLEIDLE) { 451 sendQueuedMessage(); 452 } 453 } 454 455 // internal class to hold a request message, along with the associated 456 // throttle state. 457 private static class RequestMessage { 458 459 private final int state; 460 private final DCCppMessage msg; 461 462 RequestMessage(DCCppMessage m, int s) { 463 state = s; 464 msg = m; 465 } 466 467 int getState() { 468 return state; 469 } 470 471 DCCppMessage getMsg() { 472 return msg; 473 } 474 475 } 476 477 // register for notification 478 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DCCppThrottle.class); 479 480}