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