001/** 002 * LocoNetConsist.java 003 * 004 * This is the Consist definition for a consist on a LocoNet system. it uses the 005 * LocoNet specific commands to build a consist. 006 * 007 * @author Paul Bender Copyright (C) 2011 008 */ 009package jmri.jmrix.loconet; 010 011import java.util.ArrayList; 012import jmri.Consist; 013import jmri.ConsistListener; 014import jmri.LocoAddress; 015import jmri.DccLocoAddress; 016import jmri.ThrottleListener; 017import org.slf4j.Logger; 018import org.slf4j.LoggerFactory; 019 020public class LocoNetConsist extends jmri.implementation.DccConsist implements SlotListener, ThrottleListener { 021 022 private SlotManager slotManager = null; 023 private LnTrafficController trafficController = null; 024 private jmri.jmrix.AbstractThrottleManager throttleManager = null; 025 private LocoNetSlot leadSlot = null; 026 027 private ArrayList<DccLocoAddress> needToWrite = null; 028 029 // State Machine states 030 final static int IDLESTATE = 0; 031 final static int LEADREQUESTSTATE = 1; 032 final static int LINKSTAGEONESTATE = 2; 033 final static int LINKSTAGETWOSTATE = 4; 034 final static int LINKSTAGETHREESTATE = 8; 035 final static int UNLINKSTAGEONESTATE = 16; 036 037 private int consistRequestState = IDLESTATE; 038 039 // Initialize a consist for the specific address 040 // the Default consist type for LocoNet is a Command 041 // Station Consist. 042 public LocoNetConsist(int address, LocoNetSystemConnectionMemo lm) { 043 super(address); 044 this.slotManager = lm.getSlotManager(); 045 this.trafficController = lm.getLnTrafficController(); 046 this.throttleManager = (jmri.jmrix.AbstractThrottleManager) lm.getThrottleManager(); 047 consistRequestState = LEADREQUESTSTATE; 048 consistType = Consist.CS_CONSIST; 049 needToWrite = new ArrayList<DccLocoAddress>(); 050 throttleManager.requestThrottle(consistAddress, this, false); 051 } 052 053 // Initialize a consist for the specific address 054 // the Default consist type for LocoNet is a Command 055 // Station Consist. 056 public LocoNetConsist(DccLocoAddress address, LocoNetSystemConnectionMemo lm) { 057 super(address); 058 this.slotManager = lm.getSlotManager(); 059 this.trafficController = lm.getLnTrafficController(); 060 this.throttleManager = (jmri.jmrix.AbstractThrottleManager) lm.getThrottleManager(); 061 consistRequestState = LEADREQUESTSTATE; 062 consistType = Consist.CS_CONSIST; 063 needToWrite = new ArrayList<DccLocoAddress>(); 064 throttleManager.requestThrottle(consistAddress, this, false); 065 } 066 067 // Set the Consist Type 068 @Override 069 public void setConsistType(int consist_type) { 070 if (consist_type == Consist.ADVANCED_CONSIST) { 071 consistType = consist_type; 072 return; 073 } else if (consist_type == Consist.CS_CONSIST) { 074 consistType = consist_type; 075 } else { 076 log.error("Consist Type Not Supported"); 077 notifyConsistListeners(new DccLocoAddress(0, false), ConsistListener.NotImplemented); 078 } 079 } 080 081 /* is this address allowed? 082 * On LocoNet systems, All addresses can be used in a Universal Consist 083 * and only 0 is not allowed in Advanced Consists. 084 */ 085 @Override 086 public boolean isAddressAllowed(DccLocoAddress address) { 087 if (consistType == Consist.CS_CONSIST) { 088 return true; 089 } else if (address.getNumber() != 0) { 090 return (true); 091 } else { 092 return (false); 093 } 094 } 095 096 /* is there a size limit for this consist? 097 * For LocoNet returns -1 (no limit) for 098 * both CS and Advanced Consists 099 * return 0 for any other consist type. 100 */ 101 @Override 102 public int sizeLimit() { 103 if (consistType == ADVANCED_CONSIST) { 104 return -1; 105 } else if (consistType == CS_CONSIST) { 106 return -1; 107 } else { 108 return 0; 109 } 110 } 111 112 // does the consist contain the specified address? 113 @Override 114 public boolean contains(DccLocoAddress address) { 115 if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) { 116 return consistList.contains(address); 117 } else { 118 log.error("Consist Type Not Supported"); 119 notifyConsistListeners(address, ConsistListener.NotImplemented); 120 } 121 return false; 122 } 123 124 // get the relative direction setting for a specific 125 // locomotive in the consist 126 @Override 127 public boolean getLocoDirection(DccLocoAddress address) { 128 log.debug("consist {} obtaining direction for {} Consist List Size {}", consistAddress, address, consistList.size()); 129 if (consistType == ADVANCED_CONSIST || consistType == CS_CONSIST) { 130 if (address == consistAddress) { 131 return true; 132 } 133 if (consistList.contains(address)) { 134 Boolean Direction = consistDir.get(address); 135 return (Direction.booleanValue()); 136 } else { 137 return (true); 138 } 139 } else { 140 log.error("Consist Type Not Supported"); 141 notifyConsistListeners(address, ConsistListener.NotImplemented); 142 } 143 return false; 144 } 145 146 /* 147 * Add an Address to the internal Consist list object. 148 */ 149 private synchronized void addToConsistList(DccLocoAddress LocoAddress, boolean directionNormal) { 150 Boolean Direction = Boolean.valueOf(directionNormal); 151 if (!(consistList.contains(LocoAddress))) { 152 consistList.add(LocoAddress); 153 } 154 if (consistDir.containsKey(LocoAddress)) { 155 consistDir.remove(LocoAddress); 156 } 157 consistDir.put(LocoAddress, Direction); 158 } 159 160 /* 161 * Remove an address from the internal Consist list object. 162 */ 163 private synchronized void removeFromConsistList(DccLocoAddress LocoAddress) { 164 consistDir.remove(LocoAddress); 165 consistList.remove(LocoAddress); 166 } 167 168 /* 169 * Add a Locomotive to a Consist 170 * 171 * @param address the Locomotive address to add to the locomotive 172 * @param directionNormal if the locomotive is traveling 173 * the same direction as the consist, false otherwise 174 */ 175 @Override 176 public synchronized void add(DccLocoAddress LocoAddress, boolean directionNormal) { 177 if (LocoAddress == consistAddress) { 178 // this is required for command station consists on LocoNet. 179 addToConsistList(LocoAddress, directionNormal); 180 notifyConsistListeners(LocoAddress, ConsistListener.OPERATION_SUCCESS); 181 } else if (consistType == ADVANCED_CONSIST) { 182 if (consistList.contains(LocoAddress)) { 183 // we are changing the direction, so remove first, 184 // then add 185 removeFromAdvancedConsist(LocoAddress); 186 } 187 addToConsistList(LocoAddress, directionNormal); 188 if (leadSlot == null || consistRequestState != IDLESTATE) { 189 needToWrite.add(LocoAddress); 190 } else { 191 addToAdvancedConsist(LocoAddress, directionNormal); 192 } 193 } else if (consistType == CS_CONSIST) { 194 if (consistList.contains(LocoAddress)) { 195 // we are changing the direction, so remove first, 196 // then add 197 removeFromCSConsist(LocoAddress); 198 } 199 addToConsistList(LocoAddress, directionNormal); 200 if (leadSlot == null || consistRequestState != IDLESTATE) { 201 needToWrite.add(LocoAddress); 202 } else { 203 addToCSConsist(LocoAddress, directionNormal); 204 } 205 } else { 206 log.error("Consist Type Not Supported"); 207 notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented); 208 } 209 } 210 211 private synchronized void delayedAdd() { 212 DccLocoAddress LocoAddress = needToWrite.get(0); 213 if (consistType == ADVANCED_CONSIST) { 214 addToAdvancedConsist(LocoAddress, getLocoDirection(LocoAddress)); 215 } else if (consistType == CS_CONSIST) { 216 addToCSConsist(LocoAddress, getLocoDirection(LocoAddress)); 217 } 218 needToWrite.remove(LocoAddress); 219 } 220 221 /* 222 * Restore a Locomotive to a Consist, but don't write to 223 * the command station. This is used for restoring the consist 224 * from a file or adding a consist read from the command station. 225 * 226 * @param address the Locomotive address to add to the locomotive 227 * @param directionNormal True if the locomotive is traveling 228 * the same direction as the consist, false otherwise 229 */ 230 @Override 231 public synchronized void restore(DccLocoAddress LocoAddress, boolean directionNormal) { 232 if (consistType == ADVANCED_CONSIST) { 233 addToConsistList(LocoAddress, directionNormal); 234 } else if (consistType == CS_CONSIST) { 235 addToConsistList(LocoAddress, directionNormal); 236 } else { 237 log.error("Consist Type Not Supported"); 238 notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented); 239 } 240 } 241 242 /* 243 * Remove a Locomotive from this Consist. 244 * 245 * @param address is the Locomotive address to add to the locomotive 246 */ 247 @Override 248 public synchronized void remove(DccLocoAddress LocoAddress) { 249 if (consistType == ADVANCED_CONSIST) { 250 removeFromAdvancedConsist(LocoAddress); 251 removeFromConsistList(LocoAddress); 252 } else if (consistType == CS_CONSIST) { 253 removeFromCSConsist(LocoAddress); 254 removeFromConsistList(LocoAddress); 255 } else { 256 log.error("Consist Type Not Supported"); 257 notifyConsistListeners(LocoAddress, ConsistListener.NotImplemented); 258 } 259 } 260 261 /* 262 * Add a Locomotive to an Advanced Consist. 263 * 264 * @param address the Locomotive address to add to the locomotive 265 * @param directionNormal True if the locomotive is traveling 266 * the same direction as the consist, false otherwise 267 */ 268 @Override 269 protected synchronized void addToAdvancedConsist(DccLocoAddress LocoAddress, boolean directionNormal) { 270 if (log.isDebugEnabled()) { 271 log.debug("Add Locomotive {} to advanced consist {} With Direction Normal {}.", LocoAddress.toString(), consistAddress.toString(), directionNormal); 272 } 273 //set the value in the roster entry for CV19 274 setRosterEntryCVValue(LocoAddress); 275 consistRequestState = LINKSTAGEONESTATE; 276 throttleManager.requestThrottle(LocoAddress, this, false); 277 } 278 279 /* 280 * Remove a Locomotive from an Advanced Consist 281 * @param address is the Locomotive address to add to the locomotive 282 */ 283 @Override 284 protected synchronized void removeFromAdvancedConsist(DccLocoAddress LocoAddress) { 285 if (log.isDebugEnabled()) { 286 log.debug(" Remove Locomotive {} from advanced consist {}", LocoAddress.toString(), consistAddress.toString()); 287 } 288 //reset the value in the roster entry for CV19 289 resetRosterEntryCVValue(LocoAddress); 290 slotManager.slotFromLocoAddress(LocoAddress.getNumber(), this); 291 consistRequestState = UNLINKSTAGEONESTATE; 292 } 293 294 /* 295 * Add a Locomotive to a LocoNet Universal Consist. 296 * @param address is the Locomotive address to add to the locomotive 297 * @param directionNormal is True if the locomotive is traveling 298 * the same direction as the consist, or false otherwise. 299 */ 300 private synchronized void addToCSConsist(DccLocoAddress LocoAddress, boolean directionNormal) { 301 if (log.isDebugEnabled()) { 302 log.debug("Add Locomotive {} to Standard Consist {} With Direction Normal {}.", LocoAddress.toString(), consistAddress.toString(), directionNormal); 303 } 304 if(consistList.size()<=1 && LocoAddress.equals(consistAddress)){ 305 // there is only one address in this consist, no reason to link. 306 notifyConsistListeners(LocoAddress,ConsistListener.OPERATION_SUCCESS); 307 return; 308 } 309 throttleManager.requestThrottle(LocoAddress, this, false); 310 // skip right to stage 2, we do not need to status edit. 311 consistRequestState = LINKSTAGETWOSTATE; 312 } 313 314 /* 315 * Remove a Locomotive from a LocoNet Universal Consist. 316 * @param address is the Locomotive address to add to the locomotive 317 */ 318 public synchronized void removeFromCSConsist(DccLocoAddress LocoAddress) { 319 if (log.isDebugEnabled()) { 320 log.debug("Remove Locomotive {} from Standard Consist {}.", LocoAddress.toString(), consistAddress.toString()); 321 } 322 if(consistList.size()==1 && LocoAddress.equals(consistAddress)){ 323 // there is only one address in this consist, no reason to link. 324 notifyConsistListeners(LocoAddress,ConsistListener.OPERATION_SUCCESS); 325 return; 326 } 327 slotManager.slotFromLocoAddress(LocoAddress.getNumber(), this); 328 consistRequestState = UNLINKSTAGEONESTATE; 329 } 330 331 /* 332 * create and send a message to link two slots 333 * @param lead is the slot which is the leader 334 * @param follow is the slot which will follow the leader 335 */ 336 private void linkSlots(LocoNetSlot lead, LocoNetSlot follow) { 337 LocoNetMessage msg; 338 if (lead != follow) { 339 if (slotManager.getLoconetProtocol() == LnConstants.LOCONETPROTOCOL_TWO) { 340 msg = new LocoNetMessage(6); 341 int dest1 = follow.getSlot() / 128; 342 int dest2 = follow.getSlot() % 128; 343 int src1 = lead.getSlot() / 128; 344 int src2 = lead.getSlot() % 128; 345 msg.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL); 346 msg.setElement(1, dest1 | 0b00111000); 347 msg.setElement(2, dest2 & 0x7F); 348 msg.setElement(3, src1 | 0b01000000); 349 msg.setElement(4, src2 & 0x7F); 350 } else { 351 msg = new LocoNetMessage(4); 352 msg.setOpCode(LnConstants.OPC_LINK_SLOTS); 353 msg.setElement(1, follow.getSlot()); 354 msg.setElement(2, lead.getSlot()); 355 } 356 trafficController.sendLocoNetMessage(msg); 357 } else { 358 // lead == follow 359 // this is an error, notify the consist listeners. 360 follow.removeSlotListener(this); 361 notifyConsistListeners(new DccLocoAddress(follow.locoAddr(), 362 throttleManager.canBeLongAddress(follow.locoAddr())), 363 ConsistListener.CONSIST_ERROR); 364 } 365 consistRequestState = IDLESTATE; 366 if (needToWrite.size() != 0) { 367 delayedAdd(); 368 } 369 } 370 371 /* 372 * create and send a message to unlink two slots 373 * @param lead is the slot which is the leader 374 * @param follow is the slot which was following the leader 375 */ 376 private void unlinkSlots(LocoNetSlot lead, LocoNetSlot follow) { 377 LocoNetMessage msg; 378 if (lead != follow) { 379 if (slotManager.getLoconetProtocol() == LnConstants.LOCONETPROTOCOL_TWO) { 380 msg = new LocoNetMessage(6); 381 int src1 = lead.getSlot() / 128; 382 int src2 = lead.getSlot() % 128; 383 int dest1 = follow.getSlot() / 128; 384 int dest2 = follow.getSlot() % 128; 385 msg.setOpCode(LnConstants.OPC_EXP_SLOT_MOVE_RE_OPC_IB2_SPECIAL); 386 msg.setElement(3, src1 | 0b01010000); 387 msg.setElement(4, src2 & 0x7F); 388 msg.setElement(1, dest1 | 0b00111000); 389 msg.setElement(2, dest2 & 0x7F); 390 } else { 391 msg = new LocoNetMessage(4); 392 msg.setOpCode(LnConstants.OPC_UNLINK_SLOTS); 393 msg.setElement(1, follow.getSlot()); 394 msg.setElement(2, lead.getSlot()); 395 } 396 trafficController.sendLocoNetMessage(msg); 397 } else { 398 // lead == follow 399 // this is an error, notify the consist listeners. 400 follow.removeSlotListener(this); 401 notifyConsistListeners(new DccLocoAddress(follow.locoAddr(), 402 throttleManager.canBeLongAddress(follow.locoAddr())), 403 ConsistListener.CONSIST_ERROR | ConsistListener.DELETE_ERROR ); 404 } 405 consistRequestState = IDLESTATE; 406 if (needToWrite.size() != 0) { 407 delayedAdd(); 408 } 409 } 410 411 private void setDirection(LocoNetThrottle t) { 412 log.debug("consist {} set direction for {}", consistAddress, t.getLocoAddress()); 413 // send a command to set the direction 414 // of the locomotive in the slot. 415 Boolean directionNormal = getLocoDirection((DccLocoAddress) t.getLocoAddress()); 416 if (directionNormal) { 417 t.setIsForward(leadSlot.isForward()); 418 } else { 419 t.setIsForward(!leadSlot.isForward()); 420 } 421 422 consistRequestState = LINKSTAGETWOSTATE; 423 } 424 425 private void setSlotModeAdvanced(LocoNetSlot s) { 426 // set the slot so that it can be an advanced consist 427 int oldstatus = s.slotStatus(); 428 int newstatus = oldstatus | LnConstants.STAT1_SL_SPDEX; 429 trafficController.sendLocoNetMessage(s.writeStatus(newstatus)); 430 } 431 432 // slot listener interface functions 433 @Override 434 public void notifyChangedSlot(LocoNetSlot s) { 435 log.debug("Notified slot {} changed with mode {} slot consist state: {}", s.getSlot(), consistRequestState, LnConstants.CONSIST_STAT(s.consistStatus())); 436 switch (consistRequestState) { 437 case LEADREQUESTSTATE: 438 leadSlot = s; 439 consistRequestState = IDLESTATE; 440 break; 441 case LINKSTAGEONESTATE: 442 s.addSlotListener(this); 443 setSlotModeAdvanced(s); 444 consistRequestState = LINKSTAGETWOSTATE; 445 break; 446 case LINKSTAGETWOSTATE: 447 linkSlots(leadSlot, s); 448 break; 449 case UNLINKSTAGEONESTATE: 450 unlinkSlots(leadSlot, s); 451 break; 452 default: 453 s.removeSlotListener(this); 454 notifyConsistListeners(new DccLocoAddress(s.locoAddr(), 455 throttleManager.canBeLongAddress(s.locoAddr())), 456 ConsistListener.OPERATION_SUCCESS); 457 if (needToWrite.size() != 0) { 458 delayedAdd(); 459 } else { 460 consistRequestState = IDLESTATE; 461 } 462 } 463 } 464 465 // Throttle listener interface functions 466 @Override 467 public void notifyThrottleFound(jmri.DccThrottle t) { 468 log.debug("notified Throttle {} found with mode {}", t.getLocoAddress(), consistRequestState); 469 try { 470 if (consistRequestState == LEADREQUESTSTATE) { 471 ((LocoNetThrottle) t).setIsForward(true); 472 leadSlot = ((LocoNetThrottle) t).getLocoNetSlot(); 473 consistRequestState = IDLESTATE; 474 if (needToWrite.size() != 0) { 475 delayedAdd(); 476 } 477 } else { 478 LocoNetSlot tempSlot = ((LocoNetThrottle) t).getLocoNetSlot(); 479 if (tempSlot != null) { 480 tempSlot.addSlotListener(this); 481 if (consistRequestState == LINKSTAGEONESTATE) { 482 notifyChangedSlot(tempSlot); 483 setDirection(((LocoNetThrottle) t)); 484 consistRequestState = LINKSTAGETWOSTATE; 485 } else { 486 setDirection(((LocoNetThrottle) t)); 487 } 488 } else { 489 log.error("Cannot notify a throttle's slot if the slot is null!"); 490 } 491 } 492 } catch (java.lang.ClassCastException cce) { 493 // if the simulator is in use, we will 494 // get a ClassCastException. 495 if (consistRequestState == LEADREQUESTSTATE) { 496 t.setIsForward(true); 497 consistRequestState = IDLESTATE; 498 if (needToWrite.size() != 0) { 499 delayedAdd(); 500 } 501 } else { 502 if (t instanceof LocoNetThrottle) { 503 LocoNetThrottle lt = (LocoNetThrottle)t; 504 setDirection(lt); 505 } 506 } 507 } 508 } 509 510 @Override 511 public void notifyFailedThrottleRequest(LocoAddress address, String reason) { 512 if (! (address instanceof DccLocoAddress)) { 513 throw new IllegalArgumentException("address is not a DccLocoAddress object"); 514 } 515 notifyConsistListeners((DccLocoAddress) address, 516 ConsistListener.CONSIST_ERROR); 517 removeFromConsistList((DccLocoAddress) address); 518 consistRequestState = IDLESTATE; 519 } 520 521 /** 522 * No steal or share decisions made locally 523 * <p> 524 * {@inheritDoc} 525 */ 526 @Override 527 public void notifyDecisionRequired(jmri.LocoAddress address, DecisionType question) { 528 } 529 530 private final static Logger log = LoggerFactory.getLogger(LocoNetConsist.class); 531 532}