001package jmri.implementation; 002 003import java.util.HashMap; 004import jmri.CommandStation; 005import jmri.InstanceManager; 006import jmri.NmraPacket; 007import jmri.SignalHead; 008import jmri.Turnout; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * This class implements a SignalHead that maps the various appearances values to 014 * aspect values in the <b>Extended Accessory Decoder Control Packet Format</b> 015 * and outputs that packet to the DCC System via the generic CommandStation 016 * interface 017 * <p> 018 * The mapping is as follows: 019 * <p> 020 * 0 = RED <br> 021 * 1 = YELLOW <br> 022 * 2 = GREEN <br> 023 * 3 = LUNAR <br> 024 * 4 = FLASHRED <br> 025 * 5 = FLASHYELLOW <br> 026 * 6 = FLASHGREEN <br> 027 * 7 = FLASHLUNAR <br> 028 * 8 = DARK <br> 029 * <p> 030 * The FLASH appearances are expected to be implemented in the decoder. 031 * 032 * @author Alex Shepherd Copyright (c) 2008 033 */ 034public class DccSignalHead extends AbstractSignalHead { 035 036 public DccSignalHead(String sys, String user) { 037 super(sys, user); 038 configureHead(sys); 039 } 040 041 public DccSignalHead(String sys) { 042 super(sys); 043 configureHead(sys); 044 } 045 046 private void configureHead(String sys) { 047 setDefaultOutputs(); 048 // New method separates the system name and address using $ 049 if (sys.contains("$")) { 050 dccSignalDecoderAddress = Integer.parseInt(sys.substring(sys.indexOf("$") + 1)); 051 String commandStationPrefix = sys.substring(0, sys.indexOf("$") - 1); 052 java.util.List<jmri.CommandStation> connList = jmri.InstanceManager.getList(jmri.CommandStation.class); 053 054 for (CommandStation station : connList) { 055 if (station.getSystemPrefix().equals(commandStationPrefix)) { 056 c = station; 057 break; 058 } 059 } 060 061 if (c == null) { 062 c = InstanceManager.getNullableDefault(CommandStation.class); 063 log.error("No match against the command station for {}, so will use the default", sys); 064 } 065 } else { 066 c = InstanceManager.getNullableDefault(CommandStation.class); 067 if ((sys.length() > 2) && ((sys.charAt(1) == 'H') || (sys.charAt(1) == 'h'))) { 068 dccSignalDecoderAddress = Integer.parseInt(sys.substring(2, sys.length())); 069 } else { 070 dccSignalDecoderAddress = Integer.parseInt(sys); 071 } 072 } 073 // validate the decoder address 074 // now some systems don't support this whole range 075 // also depending on how you view the NRMA spec, 1 - 2044 or 1 - 2048 076 if (dccSignalDecoderAddress < NmraPacket.accIdLowLimit || dccSignalDecoderAddress > NmraPacket.accIdAltHighLimit) { 077 log.error("SignalHead decoder address out of range: {}", dccSignalDecoderAddress); 078 throw new IllegalArgumentException("SignalHead decoder address out of range: " + dccSignalDecoderAddress); 079 } 080 } 081 082 @Override 083 public void setAppearance(int newAppearance) { 084 int oldAppearance = mAppearance; 085 mAppearance = newAppearance; 086 087 if (oldAppearance != newAppearance) { 088 updateOutput(); 089 090 // notify listeners, if any 091 firePropertyChange("Appearance", oldAppearance, newAppearance); 092 } 093 } 094 095 @Override 096 public void setLit(boolean newLit) { 097 boolean oldLit = mLit; 098 mLit = newLit; 099 if (oldLit != newLit) { 100 updateOutput(); 101 // notify listeners, if any 102 firePropertyChange("Lit", oldLit, newLit); 103 } 104 } 105 106 /** 107 * Set the held parameter. 108 * <p> 109 * Note that this does not directly affect the output on the layout; the 110 * held parameter is a local variable which affects the aspect only via 111 * higher-level logic. 112 */ 113 @Override 114 public void setHeld(boolean newHeld) { 115 boolean oldHeld = mHeld; 116 mHeld = newHeld; 117 if (oldHeld != newHeld) { 118 // notify listeners, if any 119 firePropertyChange("Held", oldHeld, newHeld); 120 } 121 } 122 123 protected void updateOutput() { 124 if (c != null) { 125 int aspect = getOutputForAppearance(SignalHead.DARK); 126 127 if (getLit()) { 128 Integer app = mAppearance; 129 if (appearanceToOutput.containsKey(app)) { 130 aspect = appearanceToOutput.get(app); 131 } else { 132 log.error("Unknown appearance {} displays DARK", mAppearance); 133 } 134 /* switch( mAppearance ){ 135 case SignalHead.DARK: aspect = 8 ; break; 136 case SignalHead.RED: aspect = 0 ; break; 137 case SignalHead.YELLOW: aspect = 1 ; break; 138 case SignalHead.GREEN: aspect = 2 ; break; 139 case SignalHead.LUNAR: aspect = 3 ; break; 140 case SignalHead.FLASHRED: aspect = 4 ; break; 141 case SignalHead.FLASHYELLOW: aspect = 5 ; break; 142 case SignalHead.FLASHGREEN: aspect = 6 ; break; 143 case SignalHead.FLASHLUNAR: aspect = 7 ; break; 144 default : aspect = 8; 145 log.error("Unknown appearance {} displays DARK", mAppearance); 146 break; 147 }*/ 148 } 149 150 151 if (useAddressOffSet) { 152 c.sendAccSignalDecoderPkt(dccSignalDecoderAddress, aspect, packetSendCount); 153 } else { 154 c.sendAltAccSignalDecoderPkt(dccSignalDecoderAddress, aspect, packetSendCount); 155 156 } 157 } 158 } 159 160 private CommandStation c; 161 162 private boolean useAddressOffSet = false; 163 164 public void useAddressOffSet(boolean boo) { 165 useAddressOffSet = boo; 166 } 167 168 public boolean useAddressOffSet() { 169 return useAddressOffSet; 170 } 171 172 protected HashMap<Integer, Integer> appearanceToOutput = new HashMap<Integer, Integer>(); 173 174 public int getOutputForAppearance(int appearance) { 175 Integer app = appearance; 176 if (!appearanceToOutput.containsKey(app)) { 177 log.error("Trying to get appearance {} but it has not been configured", appearance); 178 return -1; 179 } 180 return appearanceToOutput.get(app); 181 } 182 183 public void setOutputForAppearance(int appearance, int number) { 184 Integer app = appearance; 185 if (appearanceToOutput.containsKey(app)) { 186 log.debug("Appearance {} is already defined as {}", appearance, appearanceToOutput.get(app)); 187 appearanceToOutput.remove(app); 188 } 189 appearanceToOutput.put(app, number); 190 } 191 192 /** 193 * Create hashmap of default appearance output values. 194 */ 195 private void setDefaultOutputs() { 196 appearanceToOutput.put(SignalHead.RED, getDefaultNumberForAppearance(SignalHead.RED)); 197 appearanceToOutput.put(SignalHead.YELLOW, getDefaultNumberForAppearance(SignalHead.YELLOW)); 198 appearanceToOutput.put(SignalHead.GREEN, getDefaultNumberForAppearance(SignalHead.GREEN)); 199 appearanceToOutput.put(SignalHead.LUNAR, getDefaultNumberForAppearance(SignalHead.LUNAR)); 200 appearanceToOutput.put(SignalHead.FLASHRED, getDefaultNumberForAppearance(SignalHead.FLASHRED)); 201 appearanceToOutput.put(SignalHead.FLASHYELLOW, getDefaultNumberForAppearance(SignalHead.FLASHYELLOW)); 202 appearanceToOutput.put(SignalHead.FLASHGREEN, getDefaultNumberForAppearance(SignalHead.FLASHGREEN)); 203 appearanceToOutput.put(SignalHead.FLASHLUNAR, getDefaultNumberForAppearance(SignalHead.FLASHLUNAR)); 204 appearanceToOutput.put(SignalHead.DARK, getDefaultNumberForAppearance(SignalHead.DARK)); 205 } 206 207 public static int getDefaultNumberForAppearance(int i) { 208 switch (i) { 209 case SignalHead.DARK: 210 return 8; 211 case SignalHead.RED: 212 return 0; 213 case SignalHead.YELLOW: 214 return 1; 215 case SignalHead.GREEN: 216 return 2; 217 case SignalHead.LUNAR: 218 return 3; 219 case SignalHead.FLASHRED: 220 return 4; 221 case SignalHead.FLASHYELLOW: 222 return 5; 223 case SignalHead.FLASHGREEN: 224 return 6; 225 case SignalHead.FLASHLUNAR: 226 return 7; 227 default: 228 return 8; 229 } 230 } 231 232 private int packetSendCount = 3; 233 /** 234 * Set Number of times the packet should be sent to the track. 235 * @param count - less than 1 is treated as 1 236 */ 237 public void setDccSignalHeadPacketSendCount(int count) { 238 if (count > 0) { 239 packetSendCount = count; 240 } else { 241 packetSendCount = 1; 242 } 243 } 244 245 /** 246 * Get the number of times the packet should be sent to the track. 247 * 248 * @return the count 249 */ 250 public int getDccSignalHeadPacketSendCount() { 251 return packetSendCount; 252 } 253 254 private int dccSignalDecoderAddress; 255 256 @Override 257 public boolean isTurnoutUsed(Turnout t) { 258 return false; 259 } 260 261 private final static Logger log = LoggerFactory.getLogger(DccSignalHead.class); 262 263}