001package jmri.implementation;
002
003import javax.annotation.CheckReturnValue;
004import javax.annotation.Nonnull;
005
006import jmri.*;
007
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Abstract class providing the basic logic of the Sensor interface.
013 *
014 * @author Bob Jacobsen Copyright (C) 2001, 2009
015 */
016public abstract class AbstractSensor extends AbstractNamedBean implements Sensor {
017
018    private static final Logger log = LoggerFactory.getLogger(AbstractSensor.class);
019
020    // ctor takes a system-name string for initialization
021    public AbstractSensor(String systemName) {
022        super(systemName);
023    }
024
025    public AbstractSensor(String systemName, String userName) {
026        super(systemName, userName);
027    }
028
029    @Override
030    @Nonnull
031    public String getBeanType() {
032        return Bundle.getMessage("BeanNameSensor");
033    }
034
035    // implementing classes will typically have a function/listener to get
036    // updates from the layout, which will then call
037    //  public void firePropertyChange(String propertyName,
038    //            Object oldValue,
039    //            Object newValue)
040    // _once_ if anything has changed state
041    @Override
042    public int getKnownState() {
043        return _knownState;
044    }
045
046    protected long sensorDebounceGoingActive = 0L;
047    protected long sensorDebounceGoingInActive = 0L;
048    protected boolean useDefaultTimerSettings = false;
049
050    /**
051     * {@inheritDoc}
052     */
053    @Override
054    public void setSensorDebounceGoingActiveTimer(long time) {
055        if (sensorDebounceGoingActive == time) {
056            return;
057        }
058        long oldValue = sensorDebounceGoingActive;
059        sensorDebounceGoingActive = time;
060        firePropertyChange("ActiveTimer", oldValue, sensorDebounceGoingActive);
061    }
062
063    /**
064     * {@inheritDoc}
065     */
066    @Override
067    public long getSensorDebounceGoingActiveTimer() {
068        return sensorDebounceGoingActive;
069    }
070
071    /**
072     * {@inheritDoc}
073     */
074    @Override
075    public void setSensorDebounceGoingInActiveTimer(long time) {
076        if (sensorDebounceGoingInActive == time) {
077            return;
078        }
079        long oldValue = sensorDebounceGoingInActive;
080        sensorDebounceGoingInActive = time;
081        firePropertyChange("InActiveTimer", oldValue, sensorDebounceGoingInActive);
082    }
083
084    /**
085     * {@inheritDoc}
086     */
087    @Override
088    public long getSensorDebounceGoingInActiveTimer() {
089        return sensorDebounceGoingInActive;
090    }
091
092    @Override
093    public void setUseDefaultTimerSettings(boolean boo) {
094        if (boo == useDefaultTimerSettings) {
095            return;
096        }
097        useDefaultTimerSettings = boo;
098        if (useDefaultTimerSettings) {
099            sensorDebounceGoingActive = jmri.InstanceManager.sensorManagerInstance().getDefaultSensorDebounceGoingActive();
100            sensorDebounceGoingInActive = jmri.InstanceManager.sensorManagerInstance().getDefaultSensorDebounceGoingInActive();
101        }
102        firePropertyChange("GlobalTimer", !boo, boo);
103    }
104
105    @Override
106    public boolean getUseDefaultTimerSettings() {
107        return useDefaultTimerSettings;
108    }
109
110    protected Thread thr;
111    protected Runnable r;
112
113    /**
114     * Before going active or inactive or checking that we can go active, we will wait for
115     * sensorDebounceGoing(In)Active for things to settle down to help prevent a race condition.
116     */
117    protected void sensorDebounce() {
118        final int lastKnownState = _knownState;
119        r = () -> {
120            try {
121                long sensorDebounceTimer = sensorDebounceGoingInActive;
122                if (_rawState == ACTIVE) {
123                    sensorDebounceTimer = sensorDebounceGoingActive;
124                }
125                Thread.sleep(sensorDebounceTimer);
126                restartcount = 0;
127                _knownState = _rawState;
128
129                javax.swing.SwingUtilities.invokeAndWait(
130                        () -> firePropertyChange("KnownState", lastKnownState, _knownState)
131                );
132            } catch (InterruptedException ex) {
133                restartcount++;
134            } catch (java.lang.reflect.InvocationTargetException ex) {
135                log.error("failed to start debounced Sensor update for \"{}\" due to {}", getDisplayName(), ex.getCause().toString());
136            }
137        };
138
139        thr = jmri.util.ThreadingUtil.newThread(r , "Debounce thread " + getDisplayName() );
140        thr.start();
141    }
142
143    int restartcount = 0;
144
145    @Override
146    @Nonnull
147    @CheckReturnValue
148    public String describeState(int state) {
149        switch (state) {
150            case ACTIVE:
151                return Bundle.getMessage("SensorStateActive");
152            case INACTIVE:
153                return Bundle.getMessage("SensorStateInactive");
154            default:
155                return super.describeState(state);
156        }
157    }
158
159    /**
160     * Perform setKnownState(int) for implementations that can't actually
161     * do it on the layout. Not intended for use by implementations that can.
162     */
163    @Override
164    public void setKnownState(int newState) throws jmri.JmriException {
165        setOwnState(newState);
166    }
167
168    /**
169     * Preprocess a Sensor state change request for specific implementations
170     * of {@link #setKnownState(int)}
171     *
172     * @param newState the Sensor state command value passed
173     * @return true if a Sensor.ACTIVE was requested and Sensor is not set to _inverted
174     */
175    protected boolean stateChangeCheck(int newState) throws IllegalArgumentException {
176        // sort out states
177        if ((newState & Sensor.ACTIVE) != 0) {
178            // first look for the double case, which we can't handle
179            if ((newState & Sensor.INACTIVE) != 0) {
180                // this is the disaster case!
181                throw new IllegalArgumentException("Can't set state for Sensor " + newState);
182            } else {
183                // send an ACTIVE command (or INACTIVE if inverted)
184                return(!getInverted());
185            }
186        } else {
187            // send a INACTIVE command (or ACTIVE if inverted)
188            return(getInverted());
189        }
190    }
191
192    /**
193     * Set our internal state information, and notify bean listeners.
194     *
195     * @param s the new state
196     */
197    public void setOwnState(int s) {
198        if (_rawState != s) {
199            if (((s == ACTIVE) && (sensorDebounceGoingActive > 0))
200                    || ((s == INACTIVE) && (sensorDebounceGoingInActive > 0))) {
201
202                int oldRawState = _rawState;
203                _rawState = s;
204                if (thr != null) {
205                    thr.interrupt();
206                }
207
208                if ((restartcount != 0) && (restartcount % 10 == 0)) {
209                    log.warn("Sensor \"{}\" state keeps flapping: {}", getDisplayName(), restartcount);
210                }
211                firePropertyChange("RawState", oldRawState, s);
212                sensorDebounce();
213                return;
214            } else {
215                // we shall try to stop the thread as one of the state changes
216                // might start the thread, while the other may not.
217                if (thr != null) {
218                    thr.interrupt();
219                }
220                _rawState = s;
221            }
222        }
223        if (_knownState != s) {
224            int oldState = _knownState;
225            _knownState = s;
226            firePropertyChange("KnownState", oldState, _knownState);
227        }
228    }
229
230    @Override
231    public int getRawState() {
232        return _rawState;
233    }
234
235    /**
236     * Implement a shorter name for setKnownState.
237     * <p>
238     * This generally shouldn't be used by Java code; use setKnownState instead.
239     * The is provided to make Jython script access easier to read.
240     */
241    @Override
242    public void setState(int s) throws jmri.JmriException {
243        setKnownState(s);
244    }
245
246    /**
247     * Implement a shorter name for getKnownState.
248     * <p>
249     * This generally shouldn't be used by Java code; use getKnownState instead.
250     * The is provided to make Jython script access easier to read.
251     */
252    @Override
253    public int getState() {
254        return getKnownState();
255    }
256
257    /**
258     * Control whether the actual sensor input is considered to be inverted,
259     * e.g. the normal electrical signal that results in an ACTIVE state now
260     * results in an INACTIVE state.
261     */
262    @Override
263    public void setInverted(boolean inverted) {
264        boolean oldInverted = _inverted;
265        _inverted = inverted;
266        if (oldInverted != _inverted) {
267            firePropertyChange("inverted", oldInverted, _inverted);
268            int state = _knownState;
269            if (state == ACTIVE) {
270                setOwnState(INACTIVE);
271            } else if (state == INACTIVE) {
272                setOwnState(ACTIVE);
273            }
274        }
275    }
276
277    /**
278     * Get the inverted state. If true, the electrical signal that results in an
279     * ACTIVE state now results in an INACTIVE state.
280     * <p>
281     * Used in polling loops in system-specific code, so made final to allow
282     * optimization.
283     */
284    @Override
285    public final boolean getInverted() {
286        return _inverted;
287    }
288
289    /**
290     * By default, all implementations based on this can invert
291     */
292    @Override
293    public boolean canInvert() { return true; }
294
295    protected boolean _inverted = false;
296
297    // internal data members
298    protected int _knownState = UNKNOWN;
299    protected int _rawState = UNKNOWN;
300
301    Reporter reporter = null;
302
303    /**
304     * Some sensor boards also serve the function of being able to report back
305     * train identities via such methods as RailCom. The setting and creation of
306     * the reporter against the sensor should be done when the sensor is
307     * created. This information is not saved.
308     *
309     * @param er the reporter to set
310     */
311    @Override
312    public void setReporter(Reporter er) {
313        reporter = er;
314    }
315
316    @Override
317    public Reporter getReporter() {
318        return reporter;
319    }
320
321    /**
322     * Set the pull resistance
323     * <p>
324     * In this default implementation, the input value is ignored.
325     *
326     * @param r PullResistance value to use.
327     */
328    @Override
329    public void setPullResistance(PullResistance r){
330    }
331
332    /**
333     * Get the pull resistance.
334     *
335     * @return the currently set PullResistance value.  In this default
336     * implementation, PullResistance.PULL_OFF is always returned.
337     */
338    @Override
339    public PullResistance getPullResistance(){
340       return PullResistance.PULL_OFF;
341    }
342
343    @Override
344    public void dispose() {
345        super.dispose();
346        if (thr != null) { // try to stop the debounce thread 
347            thr.interrupt();
348        }
349    }
350
351}