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}