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 * @throws IllegalArgumentException when needed 171 */ 172 protected boolean stateChangeCheck(int newState) throws IllegalArgumentException { 173 // sort out states 174 if ((newState & Sensor.ACTIVE) != 0) { 175 // first look for the double case, which we can't handle 176 if ((newState & Sensor.INACTIVE) != 0) { 177 // this is the disaster case! 178 throw new IllegalArgumentException("Can't set state for Sensor " + newState); 179 } else { 180 // send an ACTIVE command (or INACTIVE if inverted) 181 return(!getInverted()); 182 } 183 } else { 184 // send a INACTIVE command (or ACTIVE if inverted) 185 return(getInverted()); 186 } 187 } 188 189 private static final String PROPERTY_RAW_STATE = "RawState"; 190 191 /** 192 * Set our internal state information, and notify bean listeners. 193 * 194 * @param s the new state 195 */ 196 public void setOwnState(int s) { 197 if (_rawState != s) { 198 if (((s == ACTIVE) && (sensorDebounceGoingActive > 0)) 199 || ((s == INACTIVE) && (sensorDebounceGoingInActive > 0))) { 200 201 int oldRawState = _rawState; 202 _rawState = s; 203 if (thr != null) { 204 thr.interrupt(); 205 } 206 207 if ((restartcount != 0) && (restartcount % 10 == 0)) { 208 log.warn("Sensor \"{}\" state keeps flapping: {}", getDisplayName(), restartcount); 209 } 210 firePropertyChange(PROPERTY_RAW_STATE, oldRawState, s); 211 sensorDebounce(); 212 return; 213 } else { 214 // we shall try to stop the thread as one of the state changes 215 // might start the thread, while the other may not. 216 if (thr != null) { 217 thr.interrupt(); 218 } 219 _rawState = s; 220 } 221 } 222 if (_knownState != s) { 223 int oldState = _knownState; 224 _knownState = s; 225 firePropertyChange(PROPERTY_KNOWN_STATE, oldState, _knownState); 226 } 227 } 228 229 @Override 230 public int getRawState() { 231 return _rawState; 232 } 233 234 /** 235 * Implement a shorter name for setKnownState. 236 * <p> 237 * This generally shouldn't be used by Java code; use setKnownState instead. 238 * The is provided to make Jython script access easier to read. 239 */ 240 @Override 241 public void setState(int s) throws jmri.JmriException { 242 setKnownState(s); 243 } 244 245 /** 246 * Implement a shorter name for getKnownState. 247 * <p> 248 * This generally shouldn't be used by Java code; use getKnownState instead. 249 * The is provided to make Jython script access easier to read. 250 */ 251 @Override 252 public int getState() { 253 return getKnownState(); 254 } 255 256 /** 257 * Control whether the actual sensor input is considered to be inverted, 258 * e.g. the normal electrical signal that results in an ACTIVE state now 259 * results in an INACTIVE state. 260 */ 261 @Override 262 public void setInverted(boolean inverted) { 263 boolean oldInverted = _inverted; 264 _inverted = inverted; 265 if (oldInverted != _inverted) { 266 firePropertyChange(PROPERTY_SENSOR_INVERTED, oldInverted, _inverted); 267 int state = _knownState; 268 if (state == ACTIVE) { 269 setOwnState(INACTIVE); 270 } else if (state == INACTIVE) { 271 setOwnState(ACTIVE); 272 } 273 } 274 } 275 276 /** 277 * Get the inverted state. If true, the electrical signal that results in an 278 * ACTIVE state now results in an INACTIVE state. 279 * <p> 280 * Used in polling loops in system-specific code, so made final to allow 281 * optimization. 282 */ 283 @Override 284 public final boolean getInverted() { 285 return _inverted; 286 } 287 288 /** 289 * By default, all implementations based on this can invert 290 */ 291 @Override 292 public boolean canInvert() { return true; } 293 294 protected boolean _inverted = false; 295 296 // internal data members 297 protected int _knownState = UNKNOWN; 298 protected int _rawState = UNKNOWN; 299 300 Reporter reporter = null; 301 302 /** 303 * Some sensor boards also serve the function of being able to report back 304 * train identities via such methods as RailCom. The setting and creation of 305 * the reporter against the sensor should be done when the sensor is 306 * created. This information is not saved. 307 * 308 * @param er the reporter to set 309 */ 310 @Override 311 public void setReporter(Reporter er) { 312 reporter = er; 313 } 314 315 @Override 316 public Reporter getReporter() { 317 return reporter; 318 } 319 320 /** 321 * Set the pull resistance 322 * <p> 323 * In this default implementation, the input value is ignored. 324 * 325 * @param r PullResistance value to use. 326 */ 327 @Override 328 public void setPullResistance(PullResistance r){ 329 } 330 331 /** 332 * Get the pull resistance. 333 * 334 * @return the currently set PullResistance value. In this default 335 * implementation, PullResistance.PULL_OFF is always returned. 336 */ 337 @Override 338 public PullResistance getPullResistance(){ 339 return PullResistance.PULL_OFF; 340 } 341 342 @Override 343 public void dispose() { 344 super.dispose(); 345 if (thr != null) { // try to stop the debounce thread 346 thr.interrupt(); 347 } 348 } 349 350 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AbstractSensor.class); 351 352}