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