001package jmri.jmrix.lenz; 002 003import jmri.Consist; 004import jmri.ConsistListener; 005import jmri.DccLocoAddress; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * XNetConsist.java 011 * 012 * This is the Consist definition for a consist on an XPresNet system. it uses 013 * the XpressNet specific commands to build a consist. 014 * 015 * @author Paul Bender Copyright (C) 2004-2010 016 */ 017public class XNetConsist extends jmri.implementation.DccConsist implements XNetListener { 018 019 // We need to wait for replies before completing consist 020 // operations 021 private final int IDLESTATE = 0; 022 private final int ADDREQUESTSENTSTATE = 1; 023 private final int REMOVEREQUESTSENTSTATE = 2; 024 025 private int _state = IDLESTATE; 026 027 private DccLocoAddress _locoAddress = null; // address for the last request 028 private boolean _directionNormal = false; // direction of the last request 029 030 protected XNetTrafficController tc; // hold the traffic controller associated with this consist. 031 032 /** 033 * Initialize a consist for the specific address. 034 * Default consist type is an advanced consist. 035 * @param address loco address. 036 * @param controller system connection traffic controller. 037 * @param systemMemo system connection. 038 */ 039 public XNetConsist(int address, XNetTrafficController controller, XNetSystemConnectionMemo systemMemo) { 040 super(address); 041 tc = controller; 042 this.systemMemo = systemMemo; 043 // At construction, register for messages 044 tc.addXNetListener(XNetInterface.COMMINFO 045 | XNetInterface.CONSIST, 046 this); 047 } 048 049 /** 050 * Initialize a consist for the specific address. 051 * Default consist type is an advanced consist. 052 * @param address loco address. 053 * @param controller system connection traffic controller. 054 * @param systemMemo system connection. 055 */ 056 public XNetConsist(DccLocoAddress address, XNetTrafficController controller, XNetSystemConnectionMemo systemMemo) { 057 super(address); 058 tc = controller; 059 this.systemMemo = systemMemo; 060 // At construction, register for messages 061 tc.addXNetListener(XNetInterface.COMMINFO 062 | XNetInterface.CONSIST, 063 this); 064 } 065 066 final XNetSystemConnectionMemo systemMemo; 067 068 /** 069 * Clean Up local storage, and remove the XNetListener. 070 */ 071 @Override 072 synchronized public void dispose() { 073 super.dispose(); 074 tc.removeXNetListener( 075 XNetInterface.COMMINFO 076 | XNetInterface.CONSIST, 077 this); 078 } 079 080 /** 081 * Set the Consist Type. 082 * 083 * @param consistType An integer, should be either 084 * jmri.Consist.ADVANCED_CONSIST or 085 * jmri.Consist.CS_CONSIST. 086 */ 087 @Override 088 public void setConsistType(int consistType) { 089 switch (consistType) { 090 case Consist.ADVANCED_CONSIST: 091 case Consist.CS_CONSIST: 092 this.consistType = consistType; 093 break; 094 default: 095 log.error("Consist Type Not Supported"); 096 notifyConsistListeners(new DccLocoAddress(0, false), ConsistListener.NotImplemented); 097 break; 098 } 099 } 100 101 /** 102 * Is this address allowed? 103 * <p> 104 * On Lenz systems, All addresses but 0 can be used in a consist (Either and 105 * Advanced Consist or a Double Header). 106 * 107 * @param address {@link jmri.DccLocoAddress DccLocoAddress} object to 108 * check. 109 */ 110 @Override 111 public boolean isAddressAllowed(DccLocoAddress address) { 112 return address.getNumber() != 0; 113 } 114 115 /** 116 * Is there a size limit for this consist? 117 * 118 * @return 2 For Lenz double headers. -1 (no limit) For Decoder Assisted 119 * Consists. 0 for any other consist type. 120 */ 121 @Override 122 public int sizeLimit() { 123 switch (consistType) { 124 case ADVANCED_CONSIST: 125 return -1; 126 case CS_CONSIST: 127 return 2; 128 default: 129 return 0; 130 } 131 } 132 133 /** 134 * Does the consist contain the specified address? 135 * 136 * @param address {@link jmri.DccLocoAddress DccLocoAddress} object to 137 * check. 138 */ 139 @Override 140 public boolean contains(DccLocoAddress address) { 141 if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) { 142 return (consistList.contains(address)); 143 } else { 144 log.error("Consist Type Not Supported"); 145 notifyConsistListeners(address, ConsistListener.NotImplemented); 146 } 147 return false; 148 } 149 150 /** 151 * Get the relative direction setting for a specific locomotive in the 152 * consist. 153 * 154 * @param address {@link jmri.DccLocoAddress DccLocoAddress} object to check 155 * @return true means forward, false means backwards. 156 */ 157 @Override 158 public boolean getLocoDirection(DccLocoAddress address) { 159 if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) { 160 return consistDir.get(address); 161 } else { 162 log.error("Consist Type Not Supported"); 163 notifyConsistListeners(address, ConsistListener.NotImplemented); 164 } 165 return false; 166 } 167 168 /** 169 * Add an Address to the internal consist list object. 170 * 171 * @param LocoAddress {@link jmri.DccLocoAddress address} of the 172 * locomotive to add. 173 * @param directionNormal true for normal direction, false for reverse. 174 */ 175 private synchronized void addToConsistList(DccLocoAddress LocoAddress, boolean directionNormal) { 176 if (!(consistList.contains(LocoAddress))) { 177 consistList.add(LocoAddress); 178 } 179 consistDir.put(LocoAddress, directionNormal); 180 if (consistType == CS_CONSIST && consistList.size() == 2) { 181 notifyConsistListeners(LocoAddress, 182 ConsistListener.OPERATION_SUCCESS 183 | ConsistListener.CONSIST_FULL); 184 } else { 185 notifyConsistListeners(LocoAddress, 186 ConsistListener.OPERATION_SUCCESS); 187 } 188 } 189 190 /** 191 * Remove an address from the internal consist list object. 192 * 193 * @param LocoAddress {@link jmri.DccLocoAddress address} of the locomotive 194 * to remove. 195 */ 196 private synchronized void removeFromConsistList(DccLocoAddress LocoAddress) { 197 if (consistList.contains(LocoAddress)) { 198 consistDir.remove(LocoAddress); 199 consistList.remove(LocoAddress); 200 } 201 notifyConsistListeners(LocoAddress, ConsistListener.OPERATION_SUCCESS); 202 } 203 204 /** 205 * Add a Locomotive to a Consist. 206 * 207 * @param locoAddress the Locomotive address to add to the locomotive 208 * @param directionNormal is True if the locomotive is traveling the same 209 * direction as the consist, or false otherwise 210 */ 211 @Override 212 public synchronized void add(DccLocoAddress locoAddress, boolean directionNormal) { 213 switch (consistType) { 214 case ADVANCED_CONSIST: 215 addToAdvancedConsist(locoAddress, directionNormal); 216 // save the address for the check after we get a response 217 // from the command station 218 _locoAddress = locoAddress; 219 _directionNormal = directionNormal; 220 break; 221 case CS_CONSIST: 222 if (consistList.size() < 2) { 223 // Lenz Double Headers require exactly 2 locomotives, so 224 // wait for the second locomotive to be added to start 225 if (consistList.size() == 1 && !consistList.contains(locoAddress)) { 226 addToCSConsist(locoAddress, directionNormal); 227 // save the address for the check after we get a response 228 // from the command station 229 _locoAddress = locoAddress; 230 _directionNormal = directionNormal; 231 } else if (consistList.size() < 1) { 232 // we're going to just add this directly, since we 233 // can't form the consist yet. 234 addToConsistList(locoAddress, directionNormal); 235 } else { 236 // we must have gotten here because we tried to add 237 // a locomotive already in this consist. 238 notifyConsistListeners(locoAddress, 239 ConsistListener.CONSIST_ERROR 240 | ConsistListener.ALREADY_CONSISTED); 241 } 242 } else { 243 // The only way it is valid for us to do something 244 // here is if the locomotive we're adding is 245 // already in the consist and we want to change 246 // its direction 247 if (consistList.size() == 2 248 && consistList.contains(locoAddress)) { 249 addToCSConsist(locoAddress, directionNormal); 250 // save the address for the check after we get aresponse 251 // from the command station 252 _locoAddress = locoAddress; 253 _directionNormal = directionNormal; 254 } else { 255 notifyConsistListeners(locoAddress, 256 ConsistListener.CONSIST_ERROR 257 | ConsistListener.CONSIST_FULL); 258 } 259 } 260 break; 261 default: 262 log.error("Consist Type Not Supported"); 263 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 264 break; 265 } 266 } 267 268 /** 269 * Restore a Locomotive to an Advanced Consist, but don't write to the 270 * command station. 271 * <p> 272 * This is used for restoring the consist from a file or adding a consist 273 * read from the command station. 274 * 275 * @param locoAddress the Locomotive address to add to the locomotive 276 * @param directionNormal True if the locomotive is traveling the same 277 * direction as the Consist, or false otherwise. 278 */ 279 @Override 280 public synchronized void restore(DccLocoAddress locoAddress, boolean directionNormal) { 281 switch (consistType) { 282 case ADVANCED_CONSIST: 283 case CS_CONSIST: 284 addToConsistList(locoAddress, directionNormal); 285 break; 286 default: 287 log.error("Consist Type Not Supported"); 288 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 289 break; 290 } 291 } 292 293 /** 294 * Remove a Locomotive from this Consist. 295 * 296 * @param locoAddress the Locomotive address to add to the Consist 297 */ 298 @Override 299 public synchronized void remove(DccLocoAddress locoAddress) { 300 log.debug("Consist {}: remove called for address {}", consistAddress, locoAddress); 301 switch (consistType) { 302 case ADVANCED_CONSIST: 303 // save the address for the check after we get a response 304 // from the command station 305 _locoAddress = locoAddress; 306 removeFromAdvancedConsist(locoAddress); 307 break; 308 case CS_CONSIST: 309 // Lenz Double Headers must be formed with EXACTLY 2 310 // addresses, so if there are two addresses in the list, 311 // we'll actually send the commands to remove the consist 312 if (consistList.size() == 2 313 && _state != REMOVEREQUESTSENTSTATE) { 314 // save the address for the check after we get a response 315 // from the command station 316 _locoAddress = locoAddress; 317 removeFromCSConsist(locoAddress); 318 } else { 319 // we just want to remove this from the list. 320 if (_state != REMOVEREQUESTSENTSTATE 321 || _locoAddress != locoAddress) { 322 removeFromConsistList(locoAddress); 323 } 324 } 325 break; 326 default: 327 log.error("Consist Type Not Supported"); 328 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 329 break; 330 } 331 } 332 333 /** 334 * Add a Locomotive to an Advanced Consist. 335 * 336 * @param locoAddress the Locomotive address to add to the locomotive 337 * @param directionNormal is True if the locomotive is traveling the same 338 * direction as the consist, or false otherwise. 339 */ 340 @Override 341 protected synchronized void addToAdvancedConsist(DccLocoAddress locoAddress, boolean directionNormal) { 342 log.debug("Adding locomotive {} to consist {}", locoAddress.getNumber(), consistAddress.getNumber()); 343 // First, check to see if the locomotive is in the consist already 344 if (this.contains(locoAddress)) { 345 // we want to remove the locomotive from the consist 346 // before we re-add it. (we might just be switching 347 // the direction of the locomotive in the consist) 348 removeFromAdvancedConsist(locoAddress); 349 } 350 // set the speed of the locomotive to zero, to make sure we have 351 // control over it. 352 sendDirection(locoAddress, directionNormal); 353 354 // All we have to do here is create an apropriate XNetMessage, 355 // and send it. 356 XNetMessage msg = XNetMessage.getAddLocoToConsistMsg(consistAddress.getNumber(), locoAddress.getNumber(), directionNormal); 357 tc.sendXNetMessage(msg, this); 358 _state = ADDREQUESTSENTSTATE; 359 } 360 361 /** 362 * Remove a Locomotive from an Advanced Consist. 363 * 364 * @param locoAddress the Locomotive address to add to the locomotive 365 */ 366 @Override 367 protected synchronized void removeFromAdvancedConsist(DccLocoAddress locoAddress) { 368 // set the speed of the locomotive to zero, to make sure we 369 // have control over it. 370 sendDirection(locoAddress, getLocoDirection(locoAddress)); 371 // All we have to do here is create an apropriate XNetMessage, 372 // and send it. 373 XNetMessage msg = XNetMessage.getRemoveLocoFromConsistMsg(consistAddress.getNumber(), locoAddress.getNumber()); 374 tc.sendXNetMessage(msg, this); 375 _state = REMOVEREQUESTSENTSTATE; 376 } 377 378 /** 379 * Add a Locomotive to a Lenz Double Header 380 * 381 * @param locoAddress the Locomotive address to add to the locomotive 382 * @param directionNormal is True if the locomotive is traveling the same 383 * direction as the consist, or false otherwise. 384 */ 385 private synchronized void addToCSConsist(DccLocoAddress locoAddress, boolean directionNormal) { 386 387 if (consistAddress.equals(locoAddress)) { 388 // Something went wrong here, we are trying to add a 389 // trailing locomotive to the consist with the same 390 // address as the lead locomotive. This isn't supposed to 391 // happen. 392 log.error("Attempted to add {} to consist {}", locoAddress, consistAddress); 393 _state = IDLESTATE; 394 notifyConsistListeners(_locoAddress, 395 ConsistListener.CONSIST_ERROR 396 | ConsistListener.ALREADY_CONSISTED); 397 return; 398 } 399 400 // If the consist already contains the locomotive in 401 // question, we need to disolve the consist 402 if (consistList.size() == 2 403 && consistList.contains(locoAddress)) { 404 XNetMessage msg = XNetMessage.getDisolveDoubleHeaderMsg( 405 consistList.get(0).getNumber()); 406 tc.sendXNetMessage(msg, this); 407 } 408 409 // We need to set the speed and direction of both 410 // locomotives to establish control. 411 DccLocoAddress address = consistList.get(0); 412 Boolean direction = consistDir.get(address); 413 sendDirection(address, direction); 414 sendDirection(locoAddress, directionNormal); 415 416 // All we have to do here is create an apropriate XNetMessage, 417 // and send it. 418 XNetMessage msg = XNetMessage.getBuildDoubleHeaderMsg(address.getNumber(), locoAddress.getNumber()); 419 tc.sendXNetMessage(msg, this); 420 _state = ADDREQUESTSENTSTATE; 421 422 } 423 424 /** 425 * Remove a Locomotive from a Lenz Double Header. 426 * 427 * @param LocoAddress is the Locomotive address to add to the locomotive 428 */ 429 public synchronized void removeFromCSConsist(DccLocoAddress LocoAddress) { 430 // All we have to do here is create an apropriate XNetMessage, 431 // and send it. 432 XNetMessage msg = XNetMessage.getDisolveDoubleHeaderMsg(consistList.get(0).getNumber()); 433 tc.sendXNetMessage(msg, this); 434 _state = REMOVEREQUESTSENTSTATE; 435 } 436 437 /** 438 * Listeners for messages from the command station. 439 */ 440 @Override 441 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings( value = "SLF4J_FORMAT_SHOULD_BE_CONST", 442 justification = "error message built up from parts") 443 public synchronized void message(XNetReply l) { 444 if (_state != IDLESTATE) { 445 // we're waiting for a reply, so examine what we received 446 String text; 447 if (l.isOkMessage()) { 448 if (_state == ADDREQUESTSENTSTATE) { 449 addToConsistList(_locoAddress, _directionNormal); 450 if (consistType == ADVANCED_CONSIST) { 451 //set the value in the roster entry for CV19 452 setRosterEntryCVValue(_locoAddress); 453 } 454 } else if (_state == REMOVEREQUESTSENTSTATE) { 455 if (consistType == ADVANCED_CONSIST) { 456 //reset the value in the roster entry for CV19 457 resetRosterEntryCVValue(_locoAddress); 458 } 459 removeFromConsistList(_locoAddress); 460 } 461 _state = IDLESTATE; 462 } else if (l.getElement(0) == XNetConstants.LOCO_MU_DH_ERROR) { 463 text = "XpressNet MU+DH error: "; 464 switch (l.getElement(1)) { 465 case 0x81: 466 text = text + "Selected Locomotive has not been operated by this XpressNet device or address 0 selected"; 467 log.error(text); 468 _state = IDLESTATE; 469 notifyConsistListeners(_locoAddress, 470 ConsistListener.CONSIST_ERROR 471 | ConsistListener.LOCO_NOT_OPERATED); 472 break; 473 case 0x82: 474 text = text + "Selected Locomotive is being operated by another XpressNet device"; 475 log.error(text); 476 _state = IDLESTATE; 477 notifyConsistListeners(_locoAddress, 478 ConsistListener.CONSIST_ERROR 479 | ConsistListener.LOCO_NOT_OPERATED); 480 break; 481 case 0x83: 482 text = text + "Selected Locomotive already in MU or DH"; 483 log.error(text); 484 _state = IDLESTATE; 485 notifyConsistListeners(_locoAddress, 486 ConsistListener.CONSIST_ERROR 487 | ConsistListener.ALREADY_CONSISTED); 488 break; 489 case 0x84: 490 text = text + "Unit selected for MU or DH has speed setting other than 0"; 491 log.error(text); 492 _state = IDLESTATE; 493 notifyConsistListeners(_locoAddress, 494 ConsistListener.CONSIST_ERROR 495 | ConsistListener.NONZERO_SPEED); 496 break; 497 case 0x85: 498 text = text + "Locomotive not in a MU"; 499 log.error(text); 500 _state = IDLESTATE; 501 notifyConsistListeners(_locoAddress, 502 ConsistListener.CONSIST_ERROR 503 | ConsistListener.NOT_CONSISTED); 504 log.error(text); 505 break; 506 case 0x86: 507 text = text + "Locomotive address not a multi-unit base address"; 508 log.error(text); 509 _state = IDLESTATE; 510 notifyConsistListeners(_locoAddress, 511 ConsistListener.CONSIST_ERROR 512 | ConsistListener.NOT_CONSIST_ADDR); 513 514 log.error(text); 515 break; 516 case 0x87: 517 text = text + "It is not possible to delete the locomotive"; 518 log.error(text); 519 _state = IDLESTATE; 520 notifyConsistListeners(_locoAddress, 521 ConsistListener.CONSIST_ERROR 522 | ConsistListener.DELETE_ERROR); 523 break; 524 case 0x88: 525 text = text + "The Command Station Stack is Full"; 526 log.error(text); 527 _state = IDLESTATE; 528 notifyConsistListeners(_locoAddress, 529 ConsistListener.CONSIST_ERROR 530 | ConsistListener.STACK_FULL); 531 log.error(text); 532 break; 533 default: 534 text = text + "Unknown"; 535 log.error(text); 536 _state = IDLESTATE; 537 notifyConsistListeners(_locoAddress, 538 ConsistListener.CONSIST_ERROR); 539 } 540 } 541 } 542 } 543 544 @Override 545 public void message(XNetMessage l) { 546 } 547 548 // Handle a timeout notification 549 @Override 550 public void notifyTimeout(XNetMessage msg) { 551 if (log.isDebugEnabled()) { 552 log.debug("Notified of timeout on message{}", msg.toString()); 553 } 554 } 555 556 /** 557 * Set the speed and direction of a locomotive; bypassing the commands in 558 * the throttle, since they don't work for this application. 559 * <p> 560 * For this application, we also set the speed setting to 0, which also 561 * establishes control over the locomotive in the consist. 562 * 563 * @param address the DccLocoAddress of the locomotive. 564 * @param isForward the boolean value representing the desired direction 565 */ 566 private void sendDirection(DccLocoAddress address, boolean isForward) { 567 XNetMessage msg = XNetMessage.getSpeedAndDirectionMsg(address.getNumber(), 568 jmri.SpeedStepMode.NMRA_DCC_28, 569 (float) 0.0, 570 isForward); 571 // now, we send the message to the command station 572 tc.sendXNetMessage(msg, this); 573 } 574 575 private static final Logger log = LoggerFactory.getLogger(XNetConsist.class); 576 577}