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