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