001package jmri.implementation; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.beans.PropertyChangeEvent; 005import java.beans.PropertyChangeListener; 006import jmri.NamedBeanHandle; 007import jmri.SignalHead; 008import jmri.Turnout; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Drive a single signal head via one "Turnout" object. 014 * <p> 015 * After much confusion, the user-level terminology was changed to call these 016 * "Single Output"; the class name remains the same to reduce recoding. 017 * <p> 018 * One Turnout object is provided during construction, and drives the appearance 019 * to be either ON or OFF. Normally, "THROWN" is on, and "CLOSED" is off. The 020 * facility to set the appearance via any of the basic four appearance colors + 021 * Lunar is provided, however they all do the same. 022 * <p> 023 * Based upon DoubleTurnoutSignalHead by Bob Jacobsen 024 * 025 * @author Kevin Dickerson Copyright (C) 2010 026 */ 027public class SingleTurnoutSignalHead extends DefaultSignalHead implements PropertyChangeListener { 028 029 /** 030 * Ctor including user name. 031 * 032 * @param sys system name for head 033 * @param user userName user name for head 034 * @param lit named bean for turnout switching the Lit property 035 * @param on Appearance constant from {@link jmri.SignalHead} for the 036 * output on (Turnout thrown) appearance 037 * @param off Appearance constant from {@link jmri.SignalHead} for the 038 * signal off (Turnout closed) appearance 039 */ 040 public SingleTurnoutSignalHead(String sys, String user, NamedBeanHandle<Turnout> lit, int on, int off) { 041 super(sys, user); 042 initialize(lit, on, off); 043 } 044 045 /** 046 * Ctor using only a system name. 047 * 048 * @param sys system name for head 049 * @param lit named bean for turnout switching the Lit property 050 * @param on Appearance constant from {@link jmri.SignalHead} for the 051 * output on (Turnout thrown) appearance 052 * @param off Appearance constant from {@link jmri.SignalHead} for the 053 * signal off (Turnout closed) appearance 054 */ 055 public SingleTurnoutSignalHead(String sys, NamedBeanHandle<Turnout> lit, int on, int off) { 056 super(sys); 057 initialize(lit, on, off); 058 } 059 060 /** 061 * Helper function for constructors. 062 * 063 * @param lit named bean for turnout switching the Lit property 064 * @param on Appearance constant from {@link jmri.SignalHead} for the 065 * output on (Turnout thrown) appearance 066 * @param off Appearance constant from {@link jmri.SignalHead} for the 067 * signal off (Turnout closed) appearance 068 */ 069 private void initialize(NamedBeanHandle<Turnout> lit, int on, int off) { 070 setOutput(lit); 071 mOnAppearance = on; 072 mOffAppearance = off; 073 switch (lit.getBean().getKnownState()) { 074 case jmri.Turnout.CLOSED: 075 setAppearance(off); 076 break; 077 case jmri.Turnout.THROWN: 078 setAppearance(on); 079 break; 080 default: 081 // Assumes "off" state to prevents setting turnouts at startup. 082 mAppearance = off; 083 break; 084 } 085 } 086 087 private int mOnAppearance = DARK; 088 private int mOffAppearance = LUNAR; 089 090 /** 091 * Holds the last state change we commanded our underlying turnout. 092 */ 093 private int mTurnoutCommandedState = Turnout.CLOSED; 094 095 private void setTurnoutState(int s) { 096 mTurnoutCommandedState = s; 097 mOutput.getBean().setCommandedState(s); 098 } 099 100 @Override 101 protected void updateOutput() { 102 // assumes that writing a turnout to an existing state is cheap! 103 if (!mLit) { 104 setTurnoutState(Turnout.CLOSED); 105 } else if (!mFlashOn && (mAppearance == mOnAppearance * 2)) { 106 setTurnoutState(Turnout.CLOSED); 107 } else if (!mFlashOn && (mAppearance == mOffAppearance * 2)) { 108 setTurnoutState(Turnout.THROWN); 109 } else { 110 if ((mAppearance == mOffAppearance) || (mAppearance == (mOffAppearance * 2))) { 111 setTurnoutState(Turnout.CLOSED); 112 } else if ((mAppearance == mOnAppearance) || (mAppearance == (mOnAppearance * 2))) { 113 setTurnoutState(Turnout.THROWN); 114 } else { 115 log.warn("Unexpected new appearance: {}", mAppearance); 116 } 117 } 118 } 119 120 /** 121 * Remove references to and from this object, so that it can eventually be 122 * garbage-collected. 123 */ 124 @Override 125 public void dispose() { 126 setOutput(null); 127 super.dispose(); 128 } 129 130 private NamedBeanHandle<Turnout> mOutput; 131 132 public int getOnAppearance() { 133 return mOnAppearance; 134 } 135 136 public int getOffAppearance() { 137 return mOffAppearance; 138 } 139 140 public void setOnAppearance(int on) { 141 int old = mOnAppearance; 142 mOnAppearance = on; 143 firePropertyChange("ValidStatesChanged", old, on); 144 } 145 146 public void setOffAppearance(int off) { 147 int old = mOffAppearance; 148 mOffAppearance = off; 149 firePropertyChange("ValidStatesChanged", old, off); 150 } 151 152 public NamedBeanHandle<Turnout> getOutput() { 153 return mOutput; 154 } 155 156 public void setOutput(NamedBeanHandle<Turnout> t) { 157 if (mOutput != null) { 158 mOutput.getBean().removePropertyChangeListener(this); 159 } 160 mOutput = t; 161 if (mOutput != null) { 162 mOutput.getBean().addPropertyChangeListener(this); 163 } 164 } 165 166 /** 167 * {@inheritDoc} 168 */ 169 @Override 170 public int[] getValidStates() { 171 int[] validStates; 172 if (mOnAppearance == mOffAppearance) { 173 validStates = new int[2]; 174 validStates[0] = mOnAppearance; 175 validStates[1] = mOffAppearance; 176 return validStates; 177 } if (mOnAppearance == DARK || mOffAppearance == DARK) { // we can make flashing with Dark only 178 validStates = new int[3]; 179 } else { 180 validStates = new int[2]; 181 } 182 int x = 0; 183 validStates[x] = mOnAppearance; 184 x++; 185 if (mOffAppearance == DARK) { 186 validStates[x] = (mOnAppearance * 2); // makes flashing of the one color 187 x++; 188 } 189 validStates[x] = mOffAppearance; 190 x++; 191 if (mOnAppearance == DARK) { 192 validStates[x] = (mOffAppearance * 2); // makes flashing of the one color 193 } 194 return validStates; 195 } 196 197 /** 198 * {@inheritDoc} 199 */ 200 @Override 201 public String[] getValidStateKeys() { 202 String[] validStateKeys = new String[getValidStates().length]; 203 int i = 0; 204 // use the logic coded in getValidStates() 205 for (int state : getValidStates()) { 206 validStateKeys[i++] = getSignalColorKey(state); 207 } 208 return validStateKeys; 209 } 210 211 /** 212 * {@inheritDoc} 213 */ 214 @Override 215 public String[] getValidStateNames() { 216 String[] validStateNames = new String[getValidStates().length]; 217 int i = 0; 218 // use the logic coded in getValidStates() 219 for (int state : getValidStates()) { 220 validStateNames[i++] = getSignalColorName(state); 221 } 222 return validStateNames; 223 } 224 225 @SuppressWarnings("fallthrough") 226 @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH") 227 private String getSignalColorKey(int mAppearance) { 228 switch (mAppearance) { 229 case SignalHead.RED: 230 return "SignalHeadStateRed"; 231 case SignalHead.FLASHRED: 232 return "SignalHeadStateFlashingRed"; 233 case SignalHead.YELLOW: 234 return "SignalHeadStateYellow"; 235 case SignalHead.FLASHYELLOW: 236 return "SignalHeadStateFlashingYellow"; 237 case SignalHead.GREEN: 238 return "SignalHeadStateGreen"; 239 case SignalHead.FLASHGREEN: 240 return "SignalHeadStateFlashingGreen"; 241 case SignalHead.LUNAR: 242 return "SignalHeadStateLunar"; 243 case SignalHead.FLASHLUNAR: 244 return "SignalHeadStateFlashingLunar"; 245 default: 246 log.warn("Unexpected appearance: {}", mAppearance); 247 // go dark by falling through 248 case SignalHead.DARK: 249 return "SignalHeadStateDark"; 250 } 251 } 252 253 private String getSignalColorName(int mAppearance) { 254 return Bundle.getMessage(getSignalColorKey(mAppearance)); 255 } 256 257 @Override 258 public boolean isTurnoutUsed(Turnout t) { 259 return getOutput() != null && t.equals(getOutput().getBean()); 260 } 261 262 /* (non-Javadoc) 263 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent) 264 */ 265 @Override 266 public void propertyChange(PropertyChangeEvent evt) { 267 if (evt.getSource().equals(mOutput.getBean()) && evt.getPropertyName().equals("KnownState")) { 268 // The underlying turnout has some state change. Check if its known state matches what we expected it to do. 269 int newTurnoutValue = ((Integer) evt.getNewValue()); 270 /*String oldTurnoutString = turnoutToString(mTurnoutCommandedState); 271 String newTurnoutString = turnoutToString(newTurnoutValue); 272 log.warn("signal {}: underlying turnout changed. last set state {}, current turnout state {}, current appearance {}", 273 this.mUserName, oldTurnoutString, newTurnoutString, getSignalColour(mAppearance));*/ 274 if (mTurnoutCommandedState != newTurnoutValue) { 275 // The turnout state has changed against what we commanded. 276 int oldAppearance = mAppearance; 277 int newAppearance = mAppearance; 278 if (newTurnoutValue == Turnout.CLOSED) { 279 newAppearance = mOffAppearance; 280 } 281 if (newTurnoutValue == Turnout.THROWN) { 282 newAppearance = mOnAppearance; 283 } 284 if (newAppearance != oldAppearance) { 285 mAppearance = newAppearance; 286 // Updates last commanded state. 287 mTurnoutCommandedState = newTurnoutValue; 288 // notify listeners, if any 289 firePropertyChange("Appearance", oldAppearance, newAppearance); 290 } 291 } 292 } 293 } 294 295 private final static Logger log = LoggerFactory.getLogger(SingleTurnoutSignalHead.class); 296 297}