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 * Before going active or inactive or checking that we can go active, we will wait for 111 * sensorDebounceGoing(In)Active for things to settle down to help prevent a race condition. 112 */ 113 protected void sensorDebounce() { 114 final int lastKnownState = _knownState; 115 r = () -> { 116 try { 117 long sensorDebounceTimer = sensorDebounceGoingInActive; 118 if (_rawState == ACTIVE) { 119 sensorDebounceTimer = sensorDebounceGoingActive; 120 } 121 Thread.sleep(sensorDebounceTimer); 122 restartcount = 0; 123 _knownState = _rawState; 124 125 javax.swing.SwingUtilities.invokeAndWait( 126 () -> firePropertyChange(PROPERTY_KNOWN_STATE, lastKnownState, _knownState) 127 ); 128 } catch (InterruptedException ex) { 129 restartcount++; 130 } catch (java.lang.reflect.InvocationTargetException ex) { 131 log.error("failed to start debounced Sensor update for \"{}\" due to {}", getDisplayName(), ex.getCause().toString()); 132 } 133 }; 134 135 thr = jmri.util.ThreadingUtil.newThread(r , "Debounce thread " + getDisplayName() ); 136 thr.start(); 137 } 138 139 int restartcount = 0; 140 141 @Override 142 @Nonnull 143 @CheckReturnValue 144 public String describeState(int state) { 145 switch (state) { 146 case ACTIVE: 147 return Bundle.getMessage("SensorStateActive"); 148 case INACTIVE: 149 return Bundle.getMessage("SensorStateInactive"); 150 default: 151 return super.describeState(state); 152 } 153 } 154 155 /** 156 * Perform setKnownState(int) for implementations that can't actually 157 * do it on the layout. Not intended for use by implementations that can. 158 */ 159 @Override 160 public void setKnownState(int newState) throws jmri.JmriException { 161 setOwnState(newState); 162 } 163 164 /** 165 * Preprocess a Sensor state change request for specific implementations 166 * of {@link #setKnownState(int)} 167 * 168 * @param newState the Sensor state command value passed 169 * @return true if a Sensor.ACTIVE was requested and Sensor is not set to _inverted 170 */ 171 protected boolean stateChangeCheck(int newState) throws IllegalArgumentException { 172 // sort out states 173 if ((newState & Sensor.ACTIVE) != 0) { 174 // first look for the double case, which we can't handle 175 if ((newState & Sensor.INACTIVE) != 0) { 176 // this is the disaster case! 177 throw new IllegalArgumentException("Can't set state for Sensor " + newState); 178 } else { 179 // send an ACTIVE command (or INACTIVE if inverted) 180 return(!getInverted()); 181 } 182 } else { 183 // send a INACTIVE command (or ACTIVE if inverted) 184 return(getInverted()); 185 } 186 } 187 188 private static final String PROPERTY_RAW_STATE = "RawState"; 189 190 /** 191 * Set our internal state information, and notify bean listeners. 192 * 193 * @param s the new state 194 */ 195 public void setOwnState(int s) { 196 if (_rawState != s) { 197 if (((s == ACTIVE) && (sensorDebounceGoingActive > 0)) 198 || ((s == INACTIVE) && (sensorDebounceGoingInActive > 0))) { 199 200 int oldRawState = _rawState; 201 _rawState = s; 202 if (thr != null) { 203 thr.interrupt(); 204 } 205 206 if ((restartcount != 0) && (restartcount % 10 == 0)) { 207 log.warn("Sensor \"{}\" state keeps flapping: {}", getDisplayName(), restartcount); 208 } 209 firePropertyChange(PROPERTY_RAW_STATE, oldRawState, s); 210 sensorDebounce(); 211 return; 212 } else { 213 // we shall try to stop the thread as one of the state changes 214 // might start the thread, while the other may not. 215 if (thr != null) { 216 thr.interrupt(); 217 } 218 _rawState = s; 219 } 220 } 221 if (_knownState != s) { 222 int oldState = _knownState; 223 _knownState = s; 224 firePropertyChange(PROPERTY_KNOWN_STATE, oldState, _knownState); 225 } 226 } 227 228 @Override 229 public int getRawState() { 230 return _rawState; 231 } 232 233 /** 234 * Implement a shorter name for setKnownState. 235 * <p> 236 * This generally shouldn't be used by Java code; use setKnownState instead. 237 * The is provided to make Jython script access easier to read. 238 */ 239 @Override 240 public void setState(int s) throws jmri.JmriException { 241 setKnownState(s); 242 } 243 244 /** 245 * Implement a shorter name for getKnownState. 246 * <p> 247 * This generally shouldn't be used by Java code; use getKnownState instead. 248 * The is provided to make Jython script access easier to read. 249 */ 250 @Override 251 public int getState() { 252 return getKnownState(); 253 } 254 255 /** 256 * Control whether the actual sensor input is considered to be inverted, 257 * e.g. the normal electrical signal that results in an ACTIVE state now 258 * results in an INACTIVE state. 259 */ 260 @Override 261 public void setInverted(boolean inverted) { 262 boolean oldInverted = _inverted; 263 _inverted = inverted; 264 if (oldInverted != _inverted) { 265 firePropertyChange(PROPERTY_SENSOR_INVERTED, oldInverted, _inverted); 266 int state = _knownState; 267 if (state == ACTIVE) { 268 setOwnState(INACTIVE); 269 } else if (state == INACTIVE) { 270 setOwnState(ACTIVE); 271 } 272 } 273 } 274 275 /** 276 * Get the inverted state. If true, the electrical signal that results in an 277 * ACTIVE state now results in an INACTIVE state. 278 * <p> 279 * Used in polling loops in system-specific code, so made final to allow 280 * optimization. 281 */ 282 @Override 283 public final boolean getInverted() { 284 return _inverted; 285 } 286 287 /** 288 * By default, all implementations based on this can invert 289 */ 290 @Override 291 public boolean canInvert() { return true; } 292 293 protected boolean _inverted = false; 294 295 // internal data members 296 protected int _knownState = UNKNOWN; 297 protected int _rawState = UNKNOWN; 298 299 Reporter reporter = null; 300 301 /** 302 * Some sensor boards also serve the function of being able to report back 303 * train identities via such methods as RailCom. The setting and creation of 304 * the reporter against the sensor should be done when the sensor is 305 * created. This information is not saved. 306 * 307 * @param er the reporter to set 308 */ 309 @Override 310 public void setReporter(Reporter er) { 311 reporter = er; 312 } 313 314 @Override 315 public Reporter getReporter() { 316 return reporter; 317 } 318 319 /** 320 * Set the pull resistance 321 * <p> 322 * In this default implementation, the input value is ignored. 323 * 324 * @param r PullResistance value to use. 325 */ 326 @Override 327 public void setPullResistance(PullResistance r){ 328 } 329 330 /** 331 * Get the pull resistance. 332 * 333 * @return the currently set PullResistance value. In this default 334 * implementation, PullResistance.PULL_OFF is always returned. 335 */ 336 @Override 337 public PullResistance getPullResistance(){ 338 return PullResistance.PULL_OFF; 339 } 340 341 @Override 342 public void dispose() { 343 super.dispose(); 344 if (thr != null) { // try to stop the debounce thread 345 thr.interrupt(); 346 } 347 } 348 349 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSensor.class); 350 351}