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}