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