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