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}