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}