001package jmri.implementation; 002 003import java.util.Arrays; 004import jmri.NamedBeanHandle; 005import jmri.Turnout; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * Implement SignalHead for the MERG Signal Driver 2. 011 * <p> 012 * The Signal Driver, runs off of the output of a steady State Accessory 013 * decoder. Can be configured to run 2, 3 or 4 Aspect signals. With 2 or 3 014 * aspect signals it may have a feather included. 015 * <p> 016 * The driver is designed to be used with UK based signals. 017 * <p> 018 * The class assigns turnout positions for RED, YELLOW, GREEN and Double Yellow 019 * aspects. THE SD2 does not support flashing double yellow aspects on turnouts, so 020 * an alternative method is required to do this, as per the MERG SD2 021 * documentation. 022 * <p> 023 * As there is no Double Yellow asigned within JMRI, we use the Lunar instead. 024 * <p> 025 * For more info on the signals, see 026 * <a href="http://www.merg.info">http://www.merg.info</a>. 027 * 028 * @author Kevin Dickerson Copyright (C) 2009 029 */ 030public class MergSD2SignalHead extends DefaultSignalHead { 031 032 public MergSD2SignalHead(String sys, String user, int aspect, NamedBeanHandle<Turnout> t1, NamedBeanHandle<Turnout> t2, NamedBeanHandle<Turnout> t3, boolean feather, boolean home) { 033 super(sys, user); 034 mAspects = aspect; 035 mInput1 = t1; 036 if (t2 != null) { 037 mInput2 = t2; 038 } 039 if (t3 != null) { 040 mInput3 = t3; 041 } 042 mFeather = feather; 043 mHome = home; 044 if (mHome) { 045 setAppearance(RED); 046 } else { 047 setAppearance(YELLOW); 048 } 049 } 050 051 public MergSD2SignalHead(String sys, int aspect, NamedBeanHandle<Turnout> t1, NamedBeanHandle<Turnout> t2, NamedBeanHandle<Turnout> t3, boolean feather, boolean home) { 052 super(sys); 053 mAspects = aspect; 054 mInput1 = t1; 055 if (t2 != null) { 056 mInput2 = t2; 057 } 058 if (t3 != null) { 059 mInput3 = t3; 060 } 061 mFeather = feather; 062 mHome = home; 063 if (mHome) { 064 setAppearance(RED); 065 } else { 066 setAppearance(YELLOW); 067 } 068 } 069 070 /** 071 * Set the Signal Head Appearance. 072 * Modified from DefaultSignalHead. Removed option for software flashing. 073 * 074 * @param newAppearance integer representing a valid Appearance for this head 075 */ 076 @Override 077 public void setAppearance(int newAppearance) { 078 int oldAppearance = mAppearance; 079 mAppearance = newAppearance; 080 boolean valid = false; 081 switch (mAspects) { 082 case 2: 083 if (mHome) { 084 if ((newAppearance == RED) || (newAppearance == GREEN)) { 085 valid = true; 086 } 087 } else { 088 if ((newAppearance == GREEN) || (newAppearance == YELLOW)) { 089 valid = true; 090 } 091 } 092 break; 093 case 3: 094 if ((newAppearance == RED) || (newAppearance == YELLOW) || (newAppearance == GREEN)) { 095 valid = true; 096 } 097 break; 098 case 4: 099 if ((newAppearance == RED) || (newAppearance == YELLOW) || (newAppearance == GREEN) || (newAppearance == LUNAR)) { 100 valid = true; 101 } 102 break; 103 default: 104 valid = false; 105 break; 106 } 107 if ((oldAppearance != newAppearance) && (valid)) { 108 updateOutput(); 109 110 // notify listeners, if any 111 firePropertyChange("Appearance", oldAppearance, newAppearance); 112 } 113 114 } 115 116 @Override 117 public void setLit(boolean newLit) { 118 boolean oldLit = mLit; 119 mLit = newLit; 120 if (oldLit != newLit) { 121 updateOutput(); 122 // notify listeners, if any 123 firePropertyChange("Lit", oldLit, newLit); 124 } 125 } 126 127 @Override 128 protected void updateOutput() { 129 // assumes that writing a turnout to an existing state is cheap! 130 switch (mAppearance) { 131 case RED: 132 mInput1.getBean().setCommandedState(Turnout.CLOSED); 133 //if(mInput2!=null) mInput2.setCommandedState(Turnout.CLOSED); 134 //if(mInput3!=null) mInput3.setCommandedState(Turnout.CLOSED); 135 break; 136 case YELLOW: 137 if (mHome) { 138 mInput1.getBean().setCommandedState(Turnout.THROWN); 139 if (mInput2 != null) { 140 mInput2.getBean().setCommandedState(Turnout.CLOSED); 141 } 142 } else { 143 mInput1.getBean().setCommandedState(Turnout.CLOSED); 144 } 145 break; 146 case LUNAR: 147 mInput1.getBean().setCommandedState(Turnout.THROWN); 148 mInput2.getBean().setCommandedState(Turnout.THROWN); 149 mInput3.getBean().setCommandedState(Turnout.CLOSED); 150 //mInput1.setCommandedState( 151 //mFlashYellow.setCommandedState(mFlashYellowState); 152 break; 153 case GREEN: 154 mInput1.getBean().setCommandedState(Turnout.THROWN); 155 if (mInput2 != null) { 156 mInput2.getBean().setCommandedState(Turnout.THROWN); 157 } 158 if (mInput3 != null) { 159 mInput3.getBean().setCommandedState(Turnout.THROWN); 160 } 161 break; 162 default: 163 mInput1.getBean().setCommandedState(Turnout.CLOSED); 164 165 log.warn("Unexpected new appearance: {}", mAppearance); 166 // go dark 167 } 168 //} 169 } 170 171 /** 172 * Remove references to and from this object, so that it can eventually be 173 * garbage-collected. 174 */ 175 @Override 176 public void dispose() { 177 mInput1 = null; 178 mInput2 = null; 179 mInput3 = null; 180 super.dispose(); 181 } 182 183 NamedBeanHandle<Turnout> mInput1 = null; //Section directly infront of the Signal 184 NamedBeanHandle<Turnout> mInput2 = null; //Section infront of the next Signal 185 NamedBeanHandle<Turnout> mInput3 = null; //Section infront of the second Signal 186 187 int mAspects = 2; 188 boolean mFeather = false; 189 boolean mHome = true; //Home Signal = true, Distance Signal = false 190 191 public NamedBeanHandle<Turnout> getInput1() { 192 return mInput1; 193 } 194 195 public NamedBeanHandle<Turnout> getInput2() { 196 return mInput2; 197 } 198 199 public NamedBeanHandle<Turnout> getInput3() { 200 return mInput3; 201 } 202 203 /** 204 * Return the number of aspects for this signal. 205 * 206 * @return the number of aspects 207 */ 208 public int getAspects() { 209 return mAspects; 210 } 211 212 public boolean getFeather() { 213 return mFeather; 214 } 215 216 /** 217 * Return whether this signal is a home or a distant/Repeater signal. 218 * 219 * @return true if signal is set up as Home signal (default); false if Distant 220 */ 221 public boolean getHome() { 222 return mHome; 223 } 224 225 /** 226 * Set the first turnout used on the driver. Relates to the section directly 227 * in front of the Signal {@literal (2, 3 & 4 aspect Signals)}. 228 * 229 * @param t turnout (named bean handel) to use as input 1 230 */ 231 public void setInput1(NamedBeanHandle<Turnout> t) { 232 mInput1 = t; 233 } 234 235 /** 236 * Set the second turnout used on the driver. Relates to the section in 237 * front of the next Signal (3 and 4 aspect Signal). 238 * 239 * @param t turnout (named bean handel) to use as input 2 240 */ 241 public void setInput2(NamedBeanHandle<Turnout> t) { 242 mInput2 = t; 243 } 244 245 /** 246 * Set the third turnout used on the driver. Relates to the section directly 247 * in front the third Signal (4 aspect Signal). 248 * 249 * @param t turnout (named bean handel) to use as input 3 250 */ 251 public void setInput3(NamedBeanHandle<Turnout> t) { 252 mInput3 = t; 253 } 254 255 /** 256 * Set the number of aspects on the signal. 257 * 258 * @param i the number of aspects on mast; valid values: 2, 3, 4 259 */ 260 public void setAspects(int i) { 261 mAspects = i; 262 } 263 264 public void setFeather(boolean boo) { 265 mFeather = boo; 266 } 267 268 /** 269 * Set whether the signal is a home or distance/repeater signal. 270 * 271 * @param boo true if configuring as a Home signal, false for a Distant 272 */ 273 public void setHome(boolean boo) { 274 mHome = boo; 275 } 276 277 final static private int[] validStates2AspectHome = new int[]{ 278 RED, 279 GREEN 280 }; 281 final static private String[] validStateKeys2AspectHome = new String[]{ 282 "SignalHeadStateRed", 283 "SignalHeadStateGreen" 284 }; 285 286 final static private int[] validStates2AspectDistant = new int[]{ 287 YELLOW, 288 GREEN 289 }; 290 final static private String[] validStateKeys2AspectDistant = new String[]{ 291 "SignalHeadStateYellow", 292 "SignalHeadStateGreen" 293 }; 294 295 final static private int[] validStates3Aspect = new int[]{ 296 RED, 297 YELLOW, 298 GREEN 299 }; 300 final static private String[] validStateKeys3Aspect = new String[]{ 301 "SignalHeadStateRed", 302 "SignalHeadStateYellow", 303 "SignalHeadStateGreen" 304 }; 305 306 final static private int[] validStates4Aspect = new int[]{ 307 RED, 308 YELLOW, 309 LUNAR, 310 GREEN 311 }; 312 final static private String[] validStateKeys4Aspect = new String[]{ 313 "SignalHeadStateRed", 314 "SignalHeadStateYellow", 315 "SignalHeadStateLunar", 316 "SignalHeadStateGreen" 317 }; 318 319 /** 320 * {@inheritDoc} 321 */ 322 @Override 323 public int[] getValidStates() { 324 if (!mHome) { 325 return Arrays.copyOf(validStates2AspectDistant, validStates2AspectDistant.length); 326 } else { 327 switch (mAspects) { 328 case 2: 329 return Arrays.copyOf(validStates2AspectHome, validStates2AspectHome.length); 330 case 3: 331 return Arrays.copyOf(validStates3Aspect, validStates3Aspect.length); 332 case 4: 333 return Arrays.copyOf(validStates4Aspect, validStates4Aspect.length); 334 default: 335 log.warn("Unexpected number of aspects: {}", mAspects); 336 return Arrays.copyOf(validStates3Aspect, validStates3Aspect.length); 337 } 338 } 339 } 340 341 /** 342 * {@inheritDoc} 343 */ 344 @Override 345 public String[] getValidStateKeys() { 346 if (!mHome) { 347 return Arrays.copyOf(validStateKeys2AspectDistant, validStateKeys2AspectDistant.length); 348 } else { 349 switch (mAspects) { 350 case 2: 351 return Arrays.copyOf(validStateKeys2AspectHome, validStateKeys2AspectHome.length); 352 case 3: 353 return Arrays.copyOf(validStateKeys3Aspect, validStateKeys3Aspect.length); 354 case 4: 355 return Arrays.copyOf(validStateKeys4Aspect, validStateKeys3Aspect.length); 356 default: 357 log.warn("Unexpected number of aspects: {}", mAspects); 358 return Arrays.copyOf(validStateKeys3Aspect, validStateKeys3Aspect.length); 359 } 360 } 361 } 362 363 /** 364 * {@inheritDoc} 365 */ 366 @Override 367 public String[] getValidStateNames() { 368 String[] stateNames = new String[getValidStateKeys().length]; 369 int i = 0; 370 for (String stateKey : getValidStateKeys()) { 371 stateNames[i++] = Bundle.getMessage(stateKey); 372 } 373 return stateNames; 374 } 375 376 @Override 377 boolean isTurnoutUsed(Turnout t) { 378 if (getInput1() != null && t.equals(getInput1().getBean())) { 379 return true; 380 } 381 if (getInput2() != null && t.equals(getInput2().getBean())) { 382 return true; 383 } 384 if (getInput3() != null && t.equals(getInput3().getBean())) { 385 return true; 386 } 387 return false; 388 } 389 390 private final static Logger log = LoggerFactory.getLogger(MergSD2SignalHead.class); 391 392}