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 @CheckForNull 172 private Block nextBlockOnPath(Block current){ 173 int fromdirection = current.getDirection(); 174 List<Path> thispaths = current.getPaths(); 175 for (final Path testpath : thispaths) { 176 if (testpath.checkPathSet()) { 177 Block blockTest = testpath.getBlock(); 178 int dirftTest = testpath.getFromBlockDirection(); 179 int dirtoTest = testpath.getToBlockDirection(); 180 if (directionMatch(fromdirection, dirtoTest)) { // most reliable 181 blockTest.setDirection(dirtoTest); 182 return blockTest; 183 } 184 if ((fromdirection & dirftTest) == 0) { // less reliable 185 blockTest.setDirection(dirtoTest); 186 return blockTest; 187 } 188 if ((fromdirection != dirftTest)) { // least reliable but copes with 180 degrees 189 blockTest.setDirection(dirtoTest); 190 return blockTest; 191 } 192 } 193 } 194 return null; 195 } 196 197 private static boolean directionMatch(int fromDirection, int toDirection ) { 198 return (fromDirection & toDirection) != 0; 199 } 200 201 /** 202 * Get the Next Signal Mast the locomotive is expected to pass. 203 * This value is calculated from the current block and direction 204 * of travel. 205 * 206 * @return The next SignalMast position 207 */ 208 @Override 209 public SignalMast getNextMast(){ 210 SignalMast oldNextMast = _nextMast; 211 if (_nextMast != null) { 212 _nextMast.removePropertyChangeListener(_cconSignalMastListener); 213 } 214 _nextMast=null; 215 if( getBlock() != null ) { 216 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 217 218 Block b = getBlock(); 219 Block nB = getNextBlock(); 220 while(_nextMast == null && nB !=null ) { 221 _nextMast = lbm.getFacingSignalMast(b, nB); 222 b = nB; 223 nB = nextBlockOnPath(b); 224 } 225 if ( _nextMast == null) { 226 // use block b which is the last non-null block in the path 227 _nextMast = lbm.getSignalMastAtEndBumper(b,null); 228 } 229 230 if ( _nextMast != null) { 231 // add signal changelistener 232 _cconSignalMastListener = (PropertyChangeEvent e) -> { 233 // aspect changed?, need to notify 234 firePropertyChange("MastChanged",e.getNewValue(),e.getOldValue()); 235 forwardCabSignalToLayout(); 236 }; 237 _nextMast.addPropertyChangeListener(_cconSignalMastListener); 238 } 239 } 240 if( _nextMast != null ) { 241 if ( ! _nextMast.equals(oldNextMast)) { 242 firePropertyChange("NextMast",_nextMast,oldNextMast); 243 } 244 } else { 245 // currentNextMast is null, notify if old next mast was not. 246 if ( oldNextMast != null ) { 247 firePropertyChange("NextMast",_nextMast,oldNextMast); 248 } 249 } 250 return _nextMast; 251 } 252 253 /** 254 * Get Block List to the end of Path or Signal Mast Stop, whichever first. 255 * The first Block in the list ( if any ), will be the current Block. 256 * @return list of Blocks that the loco address is expected to traverse. 257 */ 258 @Nonnull 259 @Override 260 public List<Block> getBlockList() { 261 java.util.ArrayList<Block> blockList = new java.util.ArrayList<>(); 262 LayoutBlockManager lbm = InstanceManager.getDefault(LayoutBlockManager.class); 263 Block thisBlock = getBlock(); 264 if ( thisBlock == null ) { 265 return blockList; 266 } 267 blockList.add(thisBlock); 268 Block nextBlock = nextBlockOnPath(thisBlock); 269 SignalMast mast = ( nextBlock == null ? null : lbm.getFacingSignalMast(thisBlock, nextBlock)); 270 while ( okToProceedAfterMast(mast) && nextBlock !=null ) { 271 blockList.add(nextBlock); 272 mast = lbm.getFacingSignalMast(thisBlock, nextBlock); 273 thisBlock = nextBlock; 274 nextBlock = nextBlockOnPath(thisBlock); 275 } 276 return blockList; 277 } 278 279 private boolean okToProceedAfterMast( @CheckForNull SignalMast m ) { 280 if ( m == null ) { 281 return true; 282 } 283 return !m.isAtStop(); 284 } 285 286 /** 287 * Forward the current cab signal value to the layout. 288 */ 289 @Override 290 public void forwardCabSignalToLayout() { 291 if (!isCabSignalActive() ) { 292 return; 293 } 294 if (_masterPausedButtonActive) { 295 return; 296 } 297 298 LocoAddress locoaddr = getCabSignalAddress(); 299 SignalMast mast = getNextMast(); 300 301 if (mast != null) { 302 log.debug("cab {} aspect {}",locoaddr,mast.getAspect()); 303 } 304 // and forward the message on to the layout. 305 forwardAspectToLayout(); 306 } 307 308 /** 309 * Forward the command to the layout that sets the displayed signal 310 * aspect for this address 311 */ 312 protected void forwardAspectToLayout(){ 313 // this method is to be over-written by subclasses that actually 314 // talk to layout hardware. 315 } 316 317 318 /* 319 * get whether this cab signal is on or off 320 * 321 * @return true if on, false if off 322 */ 323 @Override 324 public boolean isCabSignalActive(){ 325 return _cabSignalActive; 326 } 327 328 /* 329 * set whether this cab signal is on or off 330 * 331 * @param active true if on, false if off 332 */ 333 @Override 334 public void setCabSignalActive(boolean active){ 335 _cabSignalActive = active; 336 if(_cabSignalActive) { 337 getNextMast(); // refreshes block, mast, and sends if master button not paused 338 } 339 else { 340 resetLayoutCabSignal(); // send data invalid to layout 341 } 342 } 343 344 /* 345 * Set when initialised and when Master PAUSED button is toggled 346 * 347 * @param active true if paused, false if resumed 348 */ 349 @Override 350 public void setMasterCabSigPauseActive (boolean active) { 351 _masterPausedButtonActive = active; 352 if ( !isCabSignalActive() ){ 353 return; // if cabsig has already been disabled no action needed 354 } 355 if ( _masterPausedButtonActive ) { 356 log.debug("master paused"); 357 resetLayoutCabSignal(); // send data invalid to layout 358 } 359 else { 360 log.debug("master not paused"); 361 getNextMast(); // refreshes block, mast, and sends if single cabsig enabled 362 } 363 } 364 365 /** 366 * Forward the command to the layout that clears any displayed signal 367 * for this address 368 */ 369 protected void resetLayoutCabSignal(){ 370 // this method is to be over-written by subclasses that actually 371 // talk to layout hardware. 372 } 373 374 private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); 375 376 /** 377 * Add a listener for consist events 378 * 379 * @param l is a PropertyChangeListener object 380 */ 381 @Override 382 public synchronized void addPropertyChangeListener(PropertyChangeListener l) { 383 pcs.addPropertyChangeListener(l); 384 } 385 386 /** 387 * Remove a listener for cab signal events 388 * 389 * @param l is a PropertyChangeListener object 390 */ 391 @Override 392 public synchronized void removePropertyChangeListener(PropertyChangeListener l) { 393 pcs.removePropertyChangeListener(l); 394 } 395 396 protected void firePropertyChange(String p, Object old, Object n) { 397 log.debug("sending property {} new value {} old value {}",p,old,n); 398 pcs.firePropertyChange(p, old, n); 399 } 400 401 //PropertyChangeListener interface 402 @Override 403 public void propertyChange(PropertyChangeEvent event){ 404 if(event.getSource() instanceof Block ) { 405 String propName = event.getPropertyName(); 406 if ( Block.PROPERTY_VALUE.equals(propName)){ 407 setBlock(); // change the block. 408 } 409 410 // block value is changed before direction is set 411 if ( Block.PROPERTY_STATE.equals(propName) || Block.PROPERTY_DIRECTION.equals(propName)) { 412 // update internal state to cascade changes. 413 getNextBlock(); 414 forwardCabSignalToLayout(); 415 } 416 } else if(event.getSource() instanceof SignalMast) { 417 // update internal state to cascade changes. 418 forwardCabSignalToLayout(); 419 } 420 } 421 422 @Override 423 public String toString(){ 424 return this.getClass().getSimpleName()+" "+this.getCabSignalAddress(); 425 } 426 427 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultCabSignal.class); 428 429}