001package jmri.implementation; 002 003import java.beans.PropertyChangeEvent; 004import java.beans.PropertyChangeListener; 005import java.beans.PropertyChangeSupport; 006import java.util.List; 007import java.util.Set; 008 009import javax.annotation.CheckForNull; 010import javax.annotation.concurrent.GuardedBy; 011import javax.annotation.Nonnull; 012 013import jmri.Block; 014import jmri.BlockManager; 015import jmri.CabSignal; 016import jmri.InstanceManager; 017import jmri.LocoAddress; 018import jmri.SignalMast; 019import jmri.Path; 020import jmri.jmrit.display.layoutEditor.LayoutBlockManager; 021 022/** 023 * Default implementation of a Cab Signal Object, describing the state of the 024 * track ahead relative to a locomotive with a given address. This is 025 * effectively a mobile signal mast. 026 * 027 * @author Steve Young Copyright (C) 2018 028 * @author Paul Bender Copyright (C) 2019 029 */ 030public class DefaultCabSignal implements CabSignal, PropertyChangeListener { 031 032 private LocoAddress _address = null; 033 @GuardedBy("this") 034 private Block _currentBlock = null; 035 private Block _nextBlock = null; 036 private SignalMast _nextMast = null; 037 private boolean _cabSignalActive = true; 038 private boolean _masterPausedButtonActive = false; 039 private PropertyChangeListener _cconSignalMastListener = null; 040 041 public DefaultCabSignal(LocoAddress address){ 042 _address = address; 043 } 044 045 /** 046 * A method for cleaning up the cab signal 047 */ 048 @Override 049 @javax.annotation.OverridingMethodsMustInvokeSuper // to remove Signal Listener 050 public void dispose(){ 051 if (_nextMast != null) { 052 _nextMast.removePropertyChangeListener(_cconSignalMastListener); 053 } 054 _address = null; 055 _currentBlock = null; 056 _nextBlock = null; 057 _nextMast = null; 058 _cabSignalActive = true; 059 _masterPausedButtonActive = false; 060 } 061 062 /** 063 * Get the LocoAddress associated with the consist 064 * 065 * @return the cab signal address 066 */ 067 @Override 068 public LocoAddress getCabSignalAddress(){ 069 return _address; 070 } 071 072 /** 073 * Set the Block of the locomotive 074 * 075 * @param position is a Block the locomotive is in. 076 */ 077 @Override 078 public synchronized void setBlock(Block position){ 079 log.debug("CabSignal for {} set block {}",getCabSignalAddress(),position); 080 Block oldCurrentBlock = _currentBlock; 081 if(_currentBlock!=null){ 082 _currentBlock.removePropertyChangeListener(this); 083 } 084 _currentBlock = position; 085 if(_currentBlock!=null) { 086 _currentBlock.addPropertyChangeListener(this); 087 if(!_currentBlock.equals(oldCurrentBlock)) { 088 firePropertyChange("CurrentBlock",_currentBlock,oldCurrentBlock); 089 } 090 } else { 091 // currentblock is null, notify if old block was not. 092 if(oldCurrentBlock!=null){ 093 firePropertyChange("CurrentBlock",_currentBlock,oldCurrentBlock); 094 } 095 } 096 getNextBlock(); // calculate the next block and fire an appropriate property change. 097 // calculate the next mast and fire an appropriate property change. 098 forwardCabSignalToLayout(); 099 } 100 101 /** 102 * Set the Block of the locomotive by searching the block list. 103 */ 104 @Override 105 public synchronized void setBlock(){ 106 BlockManager bmgr = InstanceManager.getDefault(BlockManager.class); 107 Set<Block> blockSet = bmgr.getNamedBeanSet(); 108 LocoAddress addr = getCabSignalAddress(); 109 for (Block blockVal : blockSet) { 110 Object val = blockVal.getValue(); 111 if ( val != null ) { 112 log.debug("CabSignal for {} searching block {} value {}", 113 addr,blockVal,val); 114 if (val instanceof jmri.AddressedIdTag) { 115 if( ((jmri.AddressedIdTag)val).getLocoAddress().toString().equals( 116 addr.toString())){ 117 setBlock(blockVal); 118 return; 119 } 120 } else if ( val.equals(addr) || 121 val.toString().equals(addr.toString()) || 122 val.toString().equals("" + addr.getNumber())) { 123 setBlock(blockVal); 124 return; 125 } 126 } 127 } 128 // address not found in any block, set block to null 129 setBlock(null); 130 } 131 132 /** 133 * Get the Block position of the locomotive associated with the cab signal. 134 * 135 * @return The current Block position 136 */ 137 @Override 138 public synchronized Block getBlock(){ 139 return _currentBlock; 140 } 141 142 /** 143 * Get the Next Block the locomotive is expected to enter. 144 * This value is calculated from the current block and direction 145 * of travel. 146 * 147 * @return The next Block position 148 */ 149 @Override 150 public Block getNextBlock(){ 151 Block oldNextBlock = _nextBlock; 152 if(getBlock()==null){ 153 _nextBlock = null; // no current block, so can't have a next block. 154 } else { 155 _nextBlock = nextBlockOnPath(getBlock()); 156 } 157 158 if(_nextBlock!=null) { 159 if(!_nextBlock.equals(oldNextBlock)) { 160 firePropertyChange("NextBlock",_nextBlock,oldNextBlock); 161 } 162 } else { 163 // currentNextBlock is null, notify if old next block was not. 164 if(oldNextBlock!=null){ 165 firePropertyChange("NextBlock",_nextBlock,oldNextBlock); 166 } 167 } 168 return _nextBlock; 169 } 170 171 private Block nextBlockOnPath(Block current){ 172 int fromdirection = current.getDirection(); 173 List<Path> thispaths = current.getPaths(); 174 for (final Path testpath : thispaths) { 175 if (testpath.checkPathSet()) { 176 Block blockTest = testpath.getBlock(); 177 int dirftTest = testpath.getFromBlockDirection(); 178 int dirtoTest = testpath.getToBlockDirection(); 179 if (directionMatch(fromdirection, dirtoTest)) { // most reliable 180 blockTest.setDirection(dirtoTest); 181 return blockTest; 182 } 183 if ((fromdirection & dirftTest) == 0) { // less reliable 184 blockTest.setDirection(dirtoTest); 185 return blockTest; 186 } 187 if ((fromdirection != dirftTest)) { // least reliable but copes with 180 degrees 188 blockTest.setDirection(dirtoTest); 189 return blockTest; 190 } 191 } 192 } 193 return null; 194 } 195 196 private boolean directionMatch(int fromDirection, int toDirection ) { 197 return 198 (((fromDirection & Path.NORTH) != 0) && ((toDirection & Path.NORTH) != 0)) || 199 (((fromDirection & Path.SOUTH) != 0) && ((toDirection & Path.SOUTH) != 0)) || 200 (((fromDirection & Path.EAST) != 0) && ((toDirection & Path.EAST) != 0)) || 201 (((fromDirection & Path.WEST) != 0) && ((toDirection & Path.WEST) != 0)) || 202 (((fromDirection & Path.CW) != 0) && ((toDirection & Path.CW) != 0)) || 203 (((fromDirection & Path.CCW) != 0) && ((toDirection & Path.CCW) != 0)) || 204 (((fromDirection & Path.LEFT) != 0) && ((toDirection & Path.LEFT) != 0)) || 205 (((fromDirection & Path.RIGHT) != 0) && ((toDirection & Path.RIGHT) != 0)) || 206 (((fromDirection & Path.UP) != 0) && ((toDirection & Path.UP) != 0)) || 207 (((fromDirection & Path.DOWN) != 0) && ((toDirection & Path.DOWN) != 0)); 208 } 209 210 /** 211 * Get the Next Signal Mast the locomotive is expected to pass. 212 * This value is calculated from the current block and direction 213 * of travel. 214 * 215 * @return The next SignalMast position 216 */ 217 @Override 218 public SignalMast getNextMast(){ 219 SignalMast oldNextMast = _nextMast; 220 if (_nextMast != null) { 221 _nextMast.removePropertyChangeListener(_cconSignalMastListener); 222 } 223 _nextMast=null; 224 if( getBlock() != null ) { 225 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 226 227 Block b = getBlock(); 228 Block nB = getNextBlock(); 229 while(_nextMast == null && nB !=null ) { 230 _nextMast = lbm.getFacingSignalMast(b, nB); 231 b = nB; 232 nB = nextBlockOnPath(b); 233 } 234 if ( _nextMast == null) { 235 // use block b which is the last non-null block in the path 236 _nextMast = lbm.getSignalMastAtEndBumper(b,null); 237 } 238 239 if ( _nextMast != null) { 240 // add signal changelistener 241 _cconSignalMastListener = (PropertyChangeEvent e) -> { 242 // aspect changed?, need to notify 243 firePropertyChange("MastChanged",e.getNewValue(),e.getOldValue()); 244 forwardCabSignalToLayout(); 245 }; 246 _nextMast.addPropertyChangeListener(_cconSignalMastListener); 247 } 248 } 249 if( _nextMast != null ) { 250 if ( ! _nextMast.equals(oldNextMast)) { 251 firePropertyChange("NextMast",_nextMast,oldNextMast); 252 } 253 } else { 254 // currentNextMast is null, notify if old next mast was not. 255 if ( oldNextMast != null ) { 256 firePropertyChange("NextMast",_nextMast,oldNextMast); 257 } 258 } 259 return _nextMast; 260 } 261 262 /** 263 * Get Block List to the end of Path or Signal Mast Stop, whichever first. 264 * The first Block in the list ( if any ), will be the current Block. 265 * @return list of Blocks that the loco address is expected to traverse. 266 */ 267 @Nonnull 268 @Override 269 public List<Block> getBlockList() { 270 java.util.ArrayList<Block> blockList = new java.util.ArrayList<>(); 271 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 272 Block thisBlock = getBlock(); 273 if ( thisBlock == null ) { 274 return blockList; 275 } 276 blockList.add(thisBlock); 277 Block nextBlock = nextBlockOnPath(thisBlock); 278 SignalMast mast = ( nextBlock == null ? null : lbm.getFacingSignalMast(thisBlock, nextBlock)); 279 while ( okToProceedAfterMast(mast) && nextBlock !=null ) { 280 blockList.add(nextBlock); 281 mast = lbm.getFacingSignalMast(thisBlock, nextBlock); 282 thisBlock = nextBlock; 283 nextBlock = nextBlockOnPath(thisBlock); 284 } 285 return blockList; 286 } 287 288 private boolean okToProceedAfterMast( @CheckForNull SignalMast m ) { 289 if ( m == null ) { 290 return true; 291 } 292 return !m.isAtStop(); 293 } 294 295 /** 296 * Forward the current cab signal value to the layout. 297 */ 298 @Override 299 public void forwardCabSignalToLayout() { 300 if (!isCabSignalActive() ) { 301 return; 302 } 303 if (_masterPausedButtonActive) { 304 return; 305 } 306 307 LocoAddress locoaddr = getCabSignalAddress(); 308 SignalMast mast = getNextMast(); 309 310 if (mast != null) { 311 log.debug("cab {} aspect {}",locoaddr,mast.getAspect()); 312 } 313 // and forward the message on to the layout. 314 forwardAspectToLayout(); 315 } 316 317 /** 318 * Forward the command to the layout that sets the displayed signal 319 * aspect for this address 320 */ 321 protected void forwardAspectToLayout(){ 322 // this method is to be over-written by subclasses that actually 323 // talk to layout hardware. 324 } 325 326 327 /* 328 * get whether this cab signal is on or off 329 * 330 * @return true if on, false if off 331 */ 332 @Override 333 public boolean isCabSignalActive(){ 334 return _cabSignalActive; 335 } 336 337 /* 338 * set whether this cab signal is on or off 339 * 340 * @param active true if on, false if off 341 */ 342 @Override 343 public void setCabSignalActive(boolean active){ 344 _cabSignalActive = active; 345 if(_cabSignalActive) { 346 getNextMast(); // refreshes block, mast, and sends if master button not paused 347 } 348 else { 349 resetLayoutCabSignal(); // send data invalid to layout 350 } 351 } 352 353 /* 354 * Set when initialised and when Master PAUSED button is toggled 355 * 356 * @param active true if paused, false if resumed 357 */ 358 @Override 359 public void setMasterCabSigPauseActive (boolean active) { 360 _masterPausedButtonActive = active; 361 if ( !isCabSignalActive() ){ 362 return; // if cabsig has already been disabled no action needed 363 } 364 if ( _masterPausedButtonActive ) { 365 log.debug("master paused"); 366 resetLayoutCabSignal(); // send data invalid to layout 367 } 368 else { 369 log.debug("master not paused"); 370 getNextMast(); // refreshes block, mast, and sends if single cabsig enabled 371 } 372 } 373 374 /** 375 * Forward the command to the layout that clears any displayed signal 376 * for this address 377 */ 378 protected void resetLayoutCabSignal(){ 379 // this method is to be over-written by subclasses that actually 380 // talk to layout hardware. 381 } 382 383 private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 384 385 /** 386 * Add a listener for consist events 387 * 388 * @param l is a PropertyChangeListener object 389 */ 390 @Override 391 public synchronized void addPropertyChangeListener(PropertyChangeListener l) { 392 pcs.addPropertyChangeListener(l); 393 } 394 395 /** 396 * Remove a listener for cab signal events 397 * 398 * @param l is a PropertyChangeListener object 399 */ 400 @Override 401 public synchronized void removePropertyChangeListener(PropertyChangeListener l) { 402 pcs.removePropertyChangeListener(l); 403 } 404 405 protected void firePropertyChange(String p, Object old, Object n) { 406 log.debug("sending property {} new value {} old value {}",p,old,n); 407 pcs.firePropertyChange(p, old, n); 408 } 409 410 //PropertyChangeListener interface 411 @Override 412 public void propertyChange(PropertyChangeEvent event){ 413 if(event.getSource() instanceof Block) { 414 if (event.getPropertyName().equals("value")){ 415 setBlock(); // change the block. 416 } 417 418 // block value is changed before direction is set 419 if ((event.getPropertyName().equals("state")) || (event.getPropertyName().equals("direction"))) { 420 // update internal state to cascade changes. 421 getNextBlock(); 422 forwardCabSignalToLayout(); 423 } 424 } else if(event.getSource() instanceof SignalMast) { 425 // update internal state to cascade changes. 426 forwardCabSignalToLayout(); 427 } 428 } 429 430 @Override 431 public String toString(){ 432 return this.getClass().getSimpleName()+" "+this.getCabSignalAddress(); 433 } 434 435 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultCabSignal.class); 436 437}