001package jmri.implementation; 002 003import javax.annotation.CheckReturnValue; 004import javax.annotation.Nonnull; 005 006import jmri.*; 007 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Abstract class providing the basic logic of the Sensor interface. 013 * 014 * @author Bob Jacobsen Copyright (C) 2001, 2009 015 */ 016public abstract class AbstractSensor extends AbstractNamedBean implements Sensor { 017 018 private static final Logger log = LoggerFactory.getLogger(AbstractSensor.class); 019 020 // ctor takes a system-name string for initialization 021 public AbstractSensor(String systemName) { 022 super(systemName); 023 } 024 025 public AbstractSensor(String systemName, String userName) { 026 super(systemName, userName); 027 } 028 029 @Override 030 @Nonnull 031 public String getBeanType() { 032 return Bundle.getMessage("BeanNameSensor"); 033 } 034 035 // implementing classes will typically have a function/listener to get 036 // updates from the layout, which will then call 037 // public void firePropertyChange(String propertyName, 038 // Object oldValue, 039 // Object newValue) 040 // _once_ if anything has changed state 041 @Override 042 public int getKnownState() { 043 return _knownState; 044 } 045 046 protected long sensorDebounceGoingActive = 0L; 047 protected long sensorDebounceGoingInActive = 0L; 048 protected boolean useDefaultTimerSettings = false; 049 050 /** 051 * {@inheritDoc} 052 */ 053 @Override 054 public void setSensorDebounceGoingActiveTimer(long time) { 055 if (sensorDebounceGoingActive == time) { 056 return; 057 } 058 long oldValue = sensorDebounceGoingActive; 059 sensorDebounceGoingActive = time; 060 firePropertyChange("ActiveTimer", oldValue, sensorDebounceGoingActive); 061 } 062 063 /** 064 * {@inheritDoc} 065 */ 066 @Override 067 public long getSensorDebounceGoingActiveTimer() { 068 return sensorDebounceGoingActive; 069 } 070 071 /** 072 * {@inheritDoc} 073 */ 074 @Override 075 public void setSensorDebounceGoingInActiveTimer(long time) { 076 if (sensorDebounceGoingInActive == time) { 077 return; 078 } 079 long oldValue = sensorDebounceGoingInActive; 080 sensorDebounceGoingInActive = time; 081 firePropertyChange("InActiveTimer", oldValue, sensorDebounceGoingInActive); 082 } 083 084 /** 085 * {@inheritDoc} 086 */ 087 @Override 088 public long getSensorDebounceGoingInActiveTimer() { 089 return sensorDebounceGoingInActive; 090 } 091 092 @Override 093 public void setUseDefaultTimerSettings(boolean boo) { 094 if (boo == useDefaultTimerSettings) { 095 return; 096 } 097 useDefaultTimerSettings = boo; 098 if (useDefaultTimerSettings) { 099 sensorDebounceGoingActive = jmri.InstanceManager.sensorManagerInstance().getDefaultSensorDebounceGoingActive(); 100 sensorDebounceGoingInActive = jmri.InstanceManager.sensorManagerInstance().getDefaultSensorDebounceGoingInActive(); 101 } 102 firePropertyChange("GlobalTimer", !boo, boo); 103 } 104 105 @Override 106 public boolean getUseDefaultTimerSettings() { 107 return useDefaultTimerSettings; 108 } 109 110 protected Thread thr; 111 protected Runnable r; 112 113 /** 114 * Before going active or inactive or checking that we can go active, we will wait for 115 * sensorDebounceGoing(In)Active for things to settle down to help prevent a race condition. 116 */ 117 protected void sensorDebounce() { 118 final int lastKnownState = _knownState; 119 r = () -> { 120 try { 121 long sensorDebounceTimer = sensorDebounceGoingInActive; 122 if (_rawState == ACTIVE) { 123 sensorDebounceTimer = sensorDebounceGoingActive; 124 } 125 Thread.sleep(sensorDebounceTimer); 126 restartcount = 0; 127 _knownState = _rawState; 128 129 javax.swing.SwingUtilities.invokeAndWait( 130 () -> firePropertyChange("KnownState", lastKnownState, _knownState) 131 ); 132 } catch (InterruptedException ex) { 133 restartcount++; 134 } catch (java.lang.reflect.InvocationTargetException ex) { 135 log.error("failed to start debounced Sensor update for \"{}\" due to {}", getDisplayName(), ex.getCause().toString()); 136 } 137 }; 138 139 thr = jmri.util.ThreadingUtil.newThread(r , "Debounce thread " + getDisplayName() ); 140 thr.start(); 141 } 142 143 int restartcount = 0; 144 145 @Override 146 @Nonnull 147 @CheckReturnValue 148 public String describeState(int state) { 149 switch (state) { 150 case ACTIVE: 151 return Bundle.getMessage("SensorStateActive"); 152 case INACTIVE: 153 return Bundle.getMessage("SensorStateInactive"); 154 default: 155 return super.describeState(state); 156 } 157 } 158 159 /** 160 * Perform setKnownState(int) for implementations that can't actually 161 * do it on the layout. Not intended for use by implementations that can. 162 */ 163 @Override 164 public void setKnownState(int newState) throws jmri.JmriException { 165 setOwnState(newState); 166 } 167 168 /** 169 * Preprocess a Sensor state change request for specific implementations 170 * of {@link #setKnownState(int)} 171 * 172 * @param newState the Sensor state command value passed 173 * @return true if a Sensor.ACTIVE was requested and Sensor is not set to _inverted 174 */ 175 protected boolean stateChangeCheck(int newState) throws IllegalArgumentException { 176 // sort out states 177 if ((newState & Sensor.ACTIVE) != 0) { 178 // first look for the double case, which we can't handle 179 if ((newState & Sensor.INACTIVE) != 0) { 180 // this is the disaster case! 181 throw new IllegalArgumentException("Can't set state for Sensor " + newState); 182 } else { 183 // send an ACTIVE command (or INACTIVE if inverted) 184 return(!getInverted()); 185 } 186 } else { 187 // send a INACTIVE command (or ACTIVE if inverted) 188 return(getInverted()); 189 } 190 } 191 192 /** 193 * Set our internal state information, and notify bean listeners. 194 * 195 * @param s the new state 196 */ 197 public void setOwnState(int s) { 198 if (_rawState != s) { 199 if (((s == ACTIVE) && (sensorDebounceGoingActive > 0)) 200 || ((s == INACTIVE) && (sensorDebounceGoingInActive > 0))) { 201 202 int oldRawState = _rawState; 203 _rawState = s; 204 if (thr != null) { 205 thr.interrupt(); 206 } 207 208 if ((restartcount != 0) && (restartcount % 10 == 0)) { 209 log.warn("Sensor \"{}\" state keeps flapping: {}", getDisplayName(), restartcount); 210 } 211 firePropertyChange("RawState", oldRawState, s); 212 sensorDebounce(); 213 return; 214 } else { 215 // we shall try to stop the thread as one of the state changes 216 // might start the thread, while the other may not. 217 if (thr != null) { 218 thr.interrupt(); 219 } 220 _rawState = s; 221 } 222 } 223 if (_knownState != s) { 224 int oldState = _knownState; 225 _knownState = s; 226 firePropertyChange("KnownState", oldState, _knownState); 227 } 228 } 229 230 @Override 231 public int getRawState() { 232 return _rawState; 233 } 234 235 /** 236 * Implement a shorter name for setKnownState. 237 * <p> 238 * This generally shouldn't be used by Java code; use setKnownState instead. 239 * The is provided to make Jython script access easier to read. 240 */ 241 @Override 242 public void setState(int s) throws jmri.JmriException { 243 setKnownState(s); 244 } 245 246 /** 247 * Implement a shorter name for getKnownState. 248 * <p> 249 * This generally shouldn't be used by Java code; use getKnownState instead. 250 * The is provided to make Jython script access easier to read. 251 */ 252 @Override 253 public int getState() { 254 return getKnownState(); 255 } 256 257 /** 258 * Control whether the actual sensor input is considered to be inverted, 259 * e.g. the normal electrical signal that results in an ACTIVE state now 260 * results in an INACTIVE state. 261 */ 262 @Override 263 public void setInverted(boolean inverted) { 264 boolean oldInverted = _inverted; 265 _inverted = inverted; 266 if (oldInverted != _inverted) { 267 firePropertyChange("inverted", oldInverted, _inverted); 268 int state = _knownState; 269 if (state == ACTIVE) { 270 setOwnState(INACTIVE); 271 } else if (state == INACTIVE) { 272 setOwnState(ACTIVE); 273 } 274 } 275 } 276 277 /** 278 * Get the inverted state. If true, the electrical signal that results in an 279 * ACTIVE state now results in an INACTIVE state. 280 * <p> 281 * Used in polling loops in system-specific code, so made final to allow 282 * optimization. 283 */ 284 @Override 285 public final boolean getInverted() { 286 return _inverted; 287 } 288 289 /** 290 * By default, all implementations based on this can invert 291 */ 292 @Override 293 public boolean canInvert() { return true; } 294 295 protected boolean _inverted = false; 296 297 // internal data members 298 protected int _knownState = UNKNOWN; 299 protected int _rawState = UNKNOWN; 300 301 Reporter reporter = null; 302 303 /** 304 * Some sensor boards also serve the function of being able to report back 305 * train identities via such methods as RailCom. The setting and creation of 306 * the reporter against the sensor should be done when the sensor is 307 * created. This information is not saved. 308 * 309 * @param er the reporter to set 310 */ 311 @Override 312 public void setReporter(Reporter er) { 313 reporter = er; 314 } 315 316 @Override 317 public Reporter getReporter() { 318 return reporter; 319 } 320 321 /** 322 * Set the pull resistance 323 * <p> 324 * In this default implementation, the input value is ignored. 325 * 326 * @param r PullResistance value to use. 327 */ 328 @Override 329 public void setPullResistance(PullResistance r){ 330 } 331 332 /** 333 * Get the pull resistance. 334 * 335 * @return the currently set PullResistance value. In this default 336 * implementation, PullResistance.PULL_OFF is always returned. 337 */ 338 @Override 339 public PullResistance getPullResistance(){ 340 return PullResistance.PULL_OFF; 341 } 342 343 @Override 344 public void dispose() { 345 super.dispose(); 346 if (thr != null) { // try to stop the debounce thread 347 thr.interrupt(); 348 } 349 } 350 351}