001package jmri.jmrix.openlcb;
002
003import jmri.Light;
004import jmri.LightControl;
005import jmri.implementation.AbstractLight;
006import jmri.jmrix.can.CanSystemConnectionMemo;
007
008import org.openlcb.OlcbInterface;
009import org.openlcb.implementations.BitProducerConsumer;
010import org.openlcb.implementations.VersionedValueListener;
011
012import javax.annotation.CheckReturnValue;
013import javax.annotation.Nonnull;
014
015/**
016 *
017 * @author jcollell
018 */
019public final class OlcbLight extends AbstractLight {
020    
021    private static final int PC_DEFAULT_FLAGS = BitProducerConsumer.DEFAULT_FLAGS &
022            (~BitProducerConsumer.LISTEN_INVALID_STATE);
023    static final boolean DEFAULT_IS_AUTHORITATIVE = true;
024    static final boolean DEFAULT_LISTEN = true;
025    private boolean _finishedLoad = false;
026    
027    OlcbAddress addrOn;    // go to On state
028    OlcbAddress addrOff;  // go to Off state
029    private final OlcbInterface iface;
030    private final CanSystemConnectionMemo memo;
031    
032    VersionedValueListener<Boolean> lightListener;
033    BitProducerConsumer pc;
034    
035    public OlcbLight(String prefix, String address, CanSystemConnectionMemo memo) {
036        super(prefix + "L" + address);
037        this.memo = memo;
038        if (memo != null) { // greatly simplify testing
039            this.iface = memo.get(OlcbInterface.class);
040        } else {
041            this.iface = null;
042        }
043        init(address);
044    }
045
046    /**
047     * Common initialization for both constructors.
048     * <p>
049     *
050     */
051    private void init(String address) {
052        // build local addresses
053        OlcbAddress a = new OlcbAddress(address, memo);
054        OlcbAddress[] v = a.split(memo);
055        if (v == null) {
056            log.error("Did not find usable system name: {}", address);
057            return;
058        }
059        if (v.length == 2) {
060            addrOn = v[0];
061            addrOff = v[1];
062        } else {
063            log.error("Can't parse OpenLCB Light system name: {}", address);
064        }
065    }
066    
067    
068    /**
069     * Helper function that will be invoked after construction once the properties have been
070     * loaded. Used specifically for preventing double initialization when loading lights from
071     * XML.
072     */
073    void finishLoad() {
074        int flags = PC_DEFAULT_FLAGS;
075        flags = OlcbUtils.overridePCFlagsFromProperties(this, flags);
076        pc = new BitProducerConsumer(iface, addrOn.toEventID(),
077                addrOff.toEventID(), flags);
078        lightListener = new VersionedValueListener<Boolean>(pc.getValue()) {
079            @Override
080            public void update(Boolean value) {
081                setState(value ? Light.ON : Light.OFF);
082            }
083        };
084        // A Light Control will have failed to set its state during xml load
085        // as the LightListener is not present, so we re-activate any Light Controls
086        activateLight();
087    }
088
089    @Override
090    @CheckReturnValue
091    @Nonnull
092    public String getRecommendedToolTip() {
093        return addrOn.toDottedString()+";"+addrOff.toDottedString();
094    }
095    
096    /**
097     * Activate a light activating all its LightControl objects.
098     */
099    @Override
100    public void activateLight() {
101        // during xml load any Light Controls may attempt to set the Light before the
102        // lightListener has been set
103        if (lightListener==null){
104            return;
105        }
106        lightControlList.stream().forEach(LightControl::activateLightControl);
107        mActive = true; // set flag for control listeners
108        _finishedLoad = true;
109    }
110    
111    /** {@inheritDoc} */
112    @Override
113    public void setState(int newState) {
114        if (_finishedLoad){
115            super.setState(newState);
116        }
117        else {
118            log.debug("Light {} status being set while still Activating",this);
119        }
120    }
121    
122    /**
123     * Set the current state of this Light This routine requests the hardware to
124     * change to newState.
125     * @param oldState old state
126     * @param newState new state
127     */
128    @Override
129    protected void doNewState(int oldState, int newState) {
130        switch (newState) {
131            case Light.ON:
132                lightListener.setFromOwnerWithForceNotify(true);
133                break;
134            case Light.OFF:
135                lightListener.setFromOwnerWithForceNotify(false);
136                break;
137            case Light.UNKNOWN:
138                if (pc != null) {
139                    pc.resetToDefault();
140                }   break;
141            default:
142                break;
143        }
144    }
145    
146    /** {@inheritDoc} */
147    @Override
148    public void setProperty(@Nonnull String key, Object value) {
149        Object old = getProperty(key);
150        super.setProperty(key, value);
151        if (value.equals(old)) return;
152        if (pc == null) return;
153        finishLoad();
154    }
155    
156    /** {@inheritDoc} */
157    @Override
158    public void dispose() {
159        if (lightListener != null) lightListener.release();
160        if (pc != null) pc.release();
161        super.dispose();
162    }
163    
164    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OlcbLight.class);
165
166}