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