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 // consistDir.get(address) returns a Boolean, auto-unboxed to 160 // primitive boolean here. 161 return consistDir.getOrDefault(address, false); 162 } else { 163 log.error(CONSIST_TYPE_NOT_SUPPORTED); 164 notifyConsistListeners(address, ConsistListener.NotImplemented); 165 } 166 return false; 167 } 168 169 /** 170 * Add an Address to the internal consist list object. 171 * 172 * @param locoAddress {@link jmri.DccLocoAddress address} of the 173 * locomotive to add. 174 * @param directionNormal true for normal direction, false for reverse. 175 */ 176 private synchronized void addToConsistList(DccLocoAddress locoAddress, boolean directionNormal) { 177 if (!(consistList.contains(locoAddress))) { 178 consistList.add(locoAddress); 179 } 180 consistDir.put(locoAddress, directionNormal); 181 if (consistType == CS_CONSIST && consistList.size() == 2) { 182 notifyConsistListeners(locoAddress, 183 ConsistListener.OPERATION_SUCCESS 184 | ConsistListener.CONSIST_FULL); 185 } else { 186 notifyConsistListeners(locoAddress, 187 ConsistListener.OPERATION_SUCCESS); 188 } 189 } 190 191 /** 192 * Remove an address from the internal consist list object. 193 * 194 * @param locoAddress {@link jmri.DccLocoAddress address} of the locomotive 195 * to remove. 196 */ 197 private synchronized void removeFromConsistList(DccLocoAddress locoAddress) { 198 if (consistList.contains(locoAddress)) { 199 consistDir.remove(locoAddress); 200 consistList.remove(locoAddress); 201 } 202 notifyConsistListeners(locoAddress, ConsistListener.OPERATION_SUCCESS); 203 } 204 205 /** 206 * Add a Locomotive to a Consist. 207 * 208 * @param locoAddress the Locomotive address to add to the locomotive 209 * @param directionNormal is True if the locomotive is traveling the same 210 * direction as the consist, or false otherwise 211 */ 212 @Override 213 public synchronized void add(DccLocoAddress locoAddress, boolean directionNormal) { 214 switch (consistType) { 215 case ADVANCED_CONSIST: 216 addToAdvancedConsist(locoAddress, directionNormal); 217 // save the address for the check after we get a response 218 // from the command station 219 _locoAddress = locoAddress; 220 _directionNormal = directionNormal; 221 break; 222 case CS_CONSIST: 223 if (consistList.size() < 2) { 224 // Lenz Double Headers require exactly 2 locomotives, so 225 // wait for the second locomotive to be added to start 226 if (consistList.size() == 1 && !consistList.contains(locoAddress)) { 227 addToCSConsist(locoAddress, directionNormal); 228 // save the address for the check after we get a response 229 // from the command station 230 _locoAddress = locoAddress; 231 _directionNormal = directionNormal; 232 } else if (consistList.isEmpty()) { 233 // we're going to just add this directly, since we 234 // can't form the consist yet. 235 addToConsistList(locoAddress, directionNormal); 236 } else { 237 // we must have gotten here because we tried to add 238 // a locomotive already in this consist. 239 notifyConsistListeners(locoAddress, 240 ConsistListener.CONSIST_ERROR 241 | ConsistListener.ALREADY_CONSISTED); 242 } 243 } else { 244 // The only way it is valid for us to do something 245 // here is if the locomotive we're adding is 246 // already in the consist and we want to change 247 // its direction 248 if (consistList.size() == 2 249 && consistList.contains(locoAddress)) { 250 addToCSConsist(locoAddress, directionNormal); 251 // save the address for the check after we get aresponse 252 // from the command station 253 _locoAddress = locoAddress; 254 _directionNormal = directionNormal; 255 } else { 256 notifyConsistListeners(locoAddress, 257 ConsistListener.CONSIST_ERROR 258 | ConsistListener.CONSIST_FULL); 259 } 260 } 261 break; 262 default: 263 log.error(CONSIST_TYPE_NOT_SUPPORTED); 264 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 265 break; 266 } 267 } 268 269 /** 270 * Restore a Locomotive to an Advanced Consist, but don't write to the 271 * command station. 272 * <p> 273 * This is used for restoring the consist from a file or adding a consist 274 * read from the command station. 275 * 276 * @param locoAddress the Locomotive address to add to the locomotive 277 * @param directionNormal True if the locomotive is traveling the same 278 * direction as the Consist, or false otherwise. 279 */ 280 @Override 281 public synchronized void restore(DccLocoAddress locoAddress, boolean directionNormal) { 282 switch (consistType) { 283 case ADVANCED_CONSIST: 284 case CS_CONSIST: 285 addToConsistList(locoAddress, directionNormal); 286 break; 287 default: 288 log.error(CONSIST_TYPE_NOT_SUPPORTED); 289 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 290 break; 291 } 292 } 293 294 /** 295 * Remove a Locomotive from this Consist. 296 * 297 * @param locoAddress the Locomotive address to add to the Consist 298 */ 299 @Override 300 public synchronized void remove(DccLocoAddress locoAddress) { 301 log.debug("Consist {}: remove called for address {}", consistAddress, locoAddress); 302 switch (consistType) { 303 case ADVANCED_CONSIST: 304 // save the address for the check after we get a response 305 // from the command station 306 _locoAddress = locoAddress; 307 removeFromAdvancedConsist(locoAddress); 308 break; 309 case CS_CONSIST: 310 // Lenz Double Headers must be formed with EXACTLY 2 311 // addresses, so if there are two addresses in the list, 312 // we'll actually send the commands to remove the consist 313 if (consistList.size() == 2 314 && _state != REMOVEREQUESTSENTSTATE) { 315 // save the address for the check after we get a response 316 // from the command station 317 _locoAddress = locoAddress; 318 removeFromCSConsist(locoAddress); 319 } else { 320 // we just want to remove this from the list. 321 if (_state != REMOVEREQUESTSENTSTATE 322 || _locoAddress != locoAddress) { 323 removeFromConsistList(locoAddress); 324 } 325 } 326 break; 327 default: 328 log.error(CONSIST_TYPE_NOT_SUPPORTED); 329 notifyConsistListeners(locoAddress, ConsistListener.NotImplemented); 330 break; 331 } 332 } 333 334 /** 335 * Add a Locomotive to an Advanced Consist. 336 * 337 * @param locoAddress the Locomotive address to add to the locomotive 338 * @param directionNormal is True if the locomotive is traveling the same 339 * direction as the consist, or false otherwise. 340 */ 341 @Override 342 protected synchronized void addToAdvancedConsist(DccLocoAddress locoAddress, boolean directionNormal) { 343 log.debug("Adding locomotive {} to consist {}", locoAddress.getNumber(), consistAddress.getNumber()); 344 // First, check to see if the locomotive is in the consist already 345 if (this.contains(locoAddress)) { 346 // we want to remove the locomotive from the consist 347 // before we re-add it. (we might just be switching 348 // the direction of the locomotive in the consist) 349 removeFromAdvancedConsist(locoAddress); 350 } 351 // set the speed of the locomotive to zero, to make sure we have 352 // control over it. 353 sendDirection(locoAddress, directionNormal); 354 355 // All we have to do here is create an apropriate XNetMessage, 356 // and send it. 357 XNetMessage msg = XNetMessage.getAddLocoToConsistMsg(consistAddress.getNumber(), 358 locoAddress.getNumber(), directionNormal); 359 tc.sendXNetMessage(msg, this); 360 _state = ADDREQUESTSENTSTATE; 361 } 362 363 /** 364 * Remove a Locomotive from an Advanced Consist. 365 * 366 * @param locoAddress the Locomotive address to add to the locomotive 367 */ 368 @Override 369 protected synchronized void removeFromAdvancedConsist(DccLocoAddress locoAddress) { 370 // set the speed of the locomotive to zero, to make sure we 371 // have control over it. 372 sendDirection(locoAddress, getLocoDirection(locoAddress)); 373 // All we have to do here is create an apropriate XNetMessage, 374 // and send it. 375 XNetMessage msg = XNetMessage.getRemoveLocoFromConsistMsg(consistAddress.getNumber(), locoAddress.getNumber()); 376 tc.sendXNetMessage(msg, this); 377 _state = REMOVEREQUESTSENTSTATE; 378 } 379 380 /** 381 * Add a Locomotive to a Lenz Double Header 382 * 383 * @param locoAddress the Locomotive address to add to the locomotive 384 * @param directionNormal is True if the locomotive is traveling the same 385 * direction as the consist, or false otherwise. 386 */ 387 private synchronized void addToCSConsist(DccLocoAddress locoAddress, boolean directionNormal) { 388 389 if (consistAddress.equals(locoAddress)) { 390 // Something went wrong here, we are trying to add a 391 // trailing locomotive to the consist with the same 392 // address as the lead locomotive. This isn't supposed to 393 // happen. 394 log.error("Attempted to add {} to consist {}", locoAddress, consistAddress); 395 _state = IDLESTATE; 396 notifyConsistListeners(_locoAddress, 397 ConsistListener.CONSIST_ERROR 398 | ConsistListener.ALREADY_CONSISTED); 399 return; 400 } 401 402 // If the consist already contains the locomotive in 403 // question, we need to disolve the consist 404 if (consistList.size() == 2 405 && consistList.contains(locoAddress)) { 406 XNetMessage msg = XNetMessage.getDisolveDoubleHeaderMsg( 407 consistList.get(0).getNumber()); 408 tc.sendXNetMessage(msg, this); 409 } 410 411 // We need to set the speed and direction of both 412 // locomotives to establish control. 413 DccLocoAddress address = consistList.get(0); 414 Boolean direction = consistDir.get(address); 415 sendDirection(address, direction); 416 sendDirection(locoAddress, directionNormal); 417 418 // All we have to do here is create an apropriate XNetMessage, 419 // and send it. 420 XNetMessage msg = XNetMessage.getBuildDoubleHeaderMsg(address.getNumber(), locoAddress.getNumber()); 421 tc.sendXNetMessage(msg, this); 422 _state = ADDREQUESTSENTSTATE; 423 424 } 425 426 /** 427 * Remove a Locomotive from a Lenz Double Header.locoAddress 428 * @param locoAddress is the Locomotive address, unused here. 429 */ 430 public synchronized void removeFromCSConsist(DccLocoAddress locoAddress) { 431 // All we have to do here is create an apropriate XNetMessage, 432 // and send it. 433 XNetMessage msg = XNetMessage.getDisolveDoubleHeaderMsg(consistList.get(0).getNumber()); 434 tc.sendXNetMessage(msg, this); 435 _state = REMOVEREQUESTSENTSTATE; 436 } 437 438 /** 439 * Listeners for messages from the command station. 440 */ 441 @Override 442 public synchronized void message(XNetReply l) { 443 // remove() for ADVANCED_CONSIST is fire-and-forget: it sends the 444 // XNet message and returns immediately, with the actual list/state 445 // update happening later in this callback. dispose() does not wait 446 // for those replies before nulling out consistList, so a reply for 447 // an already-disposed consist must be ignored here rather than 448 // acting on torn-down state. 449 if (consistList == null) { 450 log.debug("Ignoring reply for consist {} -- already disposed", consistAddress); 451 return; 452 } 453 if (_state != IDLESTATE) { 454 // we're waiting for a reply, so examine what we received 455 if (l.isOkMessage()) { 456 if (_state == ADDREQUESTSENTSTATE) { 457 addToConsistList(_locoAddress, _directionNormal); 458 if (consistType == ADVANCED_CONSIST) { 459 //set the value in the roster entry for CV19 460 setRosterEntryCVValue(_locoAddress); 461 } 462 } else if (_state == REMOVEREQUESTSENTSTATE) { 463 if (consistType == ADVANCED_CONSIST) { 464 //reset the value in the roster entry for CV19 465 resetRosterEntryCVValue(_locoAddress); 466 } 467 removeFromConsistList(_locoAddress); 468 } 469 _state = IDLESTATE; 470 } else if (l.getElement(0) == XNetConstants.LOCO_MU_DH_ERROR) { 471 String text; 472 switch (l.getElement(1)) { 473 case 0x81: 474 text = "Selected Locomotive has not been operated by this XpressNet device or address 0 selected"; 475 _state = IDLESTATE; 476 notifyConsistListeners(_locoAddress, 477 ConsistListener.CONSIST_ERROR 478 | ConsistListener.LOCO_NOT_OPERATED); 479 break; 480 case 0x82: 481 text = "Selected Locomotive is being operated by another XpressNet device"; 482 _state = IDLESTATE; 483 notifyConsistListeners(_locoAddress, 484 ConsistListener.CONSIST_ERROR 485 | ConsistListener.LOCO_NOT_OPERATED); 486 break; 487 case 0x83: 488 text = "Selected Locomotive already in MU or DH"; 489 _state = IDLESTATE; 490 notifyConsistListeners(_locoAddress, 491 ConsistListener.CONSIST_ERROR 492 | ConsistListener.ALREADY_CONSISTED); 493 break; 494 case 0x84: 495 text = "Unit selected for MU or DH has speed setting other than 0"; 496 _state = IDLESTATE; 497 notifyConsistListeners(_locoAddress, 498 ConsistListener.CONSIST_ERROR 499 | ConsistListener.NONZERO_SPEED); 500 break; 501 case 0x85: 502 text = "Locomotive not in a MU"; 503 _state = IDLESTATE; 504 notifyConsistListeners(_locoAddress, 505 ConsistListener.CONSIST_ERROR 506 | ConsistListener.NOT_CONSISTED); 507 break; 508 case 0x86: 509 text = "Locomotive address not a multi-unit base address"; 510 _state = IDLESTATE; 511 notifyConsistListeners(_locoAddress, 512 ConsistListener.CONSIST_ERROR 513 | ConsistListener.NOT_CONSIST_ADDR); 514 515 break; 516 case 0x87: 517 text = "It is not possible to delete the locomotive"; 518 _state = IDLESTATE; 519 notifyConsistListeners(_locoAddress, 520 ConsistListener.CONSIST_ERROR 521 | ConsistListener.DELETE_ERROR); 522 break; 523 case 0x88: 524 text = "The Command Station Stack is Full"; 525 _state = IDLESTATE; 526 notifyConsistListeners(_locoAddress, 527 ConsistListener.CONSIST_ERROR 528 | ConsistListener.STACK_FULL); 529 break; 530 default: 531 text = "Unknown"; 532 _state = IDLESTATE; 533 notifyConsistListeners(_locoAddress, 534 ConsistListener.CONSIST_ERROR); 535 } 536 log.error("XpressNet MU+DH error: {}",text); 537 } 538 } 539 } 540 541 @Override 542 public void message(XNetMessage l) { 543 // we don't care about outgoing messages 544 } 545 546 // Handle a timeout notification 547 @Override 548 public void notifyTimeout(XNetMessage msg) { 549 log.debug("Notified of timeout on message{}", msg); 550 } 551 552 /** 553 * Set the speed and direction of a locomotive; bypassing the commands in 554 * the throttle, since they don't work for this application. 555 * <p> 556 * For this application, we also set the speed setting to 0, which also 557 * establishes control over the locomotive in the consist. 558 * 559 * @param address the DccLocoAddress of the locomotive. 560 * @param isForward the boolean value representing the desired direction 561 */ 562 private void sendDirection(DccLocoAddress address, boolean isForward) { 563 XNetMessage msg = XNetMessage.getSpeedAndDirectionMsg(address.getNumber(), 564 jmri.SpeedStepMode.NMRA_DCC_28, 565 (float) 0.0, 566 isForward); 567 // now, we send the message to the command station 568 tc.sendXNetMessage(msg, this); 569 } 570 571 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(XNetConsist.class); 572 573}