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 byte[] sigPacket; 151 if (useAddressOffSet) { 152 sigPacket = NmraPacket.accSignalDecoderPkt(dccSignalDecoderAddress, aspect); 153 } else { 154 sigPacket = NmraPacket.altAccSignalDecoderPkt(dccSignalDecoderAddress, aspect); 155 } 156 if (sigPacket != null) { 157 c.sendPacket(sigPacket, packetSendCount); 158 } 159 } 160 } 161 162 private CommandStation c; 163 164 private boolean useAddressOffSet = false; 165 166 public void useAddressOffSet(boolean boo) { 167 useAddressOffSet = boo; 168 } 169 170 public boolean useAddressOffSet() { 171 return useAddressOffSet; 172 } 173 174 protected HashMap<Integer, Integer> appearanceToOutput = new HashMap<Integer, Integer>(); 175 176 public int getOutputForAppearance(int appearance) { 177 Integer app = appearance; 178 if (!appearanceToOutput.containsKey(app)) { 179 log.error("Trying to get appearance {} but it has not been configured", appearance); 180 return -1; 181 } 182 return appearanceToOutput.get(app); 183 } 184 185 public void setOutputForAppearance(int appearance, int number) { 186 Integer app = appearance; 187 if (appearanceToOutput.containsKey(app)) { 188 log.debug("Appearance {} is already defined as {}", appearance, appearanceToOutput.get(app)); 189 appearanceToOutput.remove(app); 190 } 191 appearanceToOutput.put(app, number); 192 } 193 194 /** 195 * Create hashmap of default appearance output values. 196 */ 197 private void setDefaultOutputs() { 198 appearanceToOutput.put(SignalHead.RED, getDefaultNumberForAppearance(SignalHead.RED)); 199 appearanceToOutput.put(SignalHead.YELLOW, getDefaultNumberForAppearance(SignalHead.YELLOW)); 200 appearanceToOutput.put(SignalHead.GREEN, getDefaultNumberForAppearance(SignalHead.GREEN)); 201 appearanceToOutput.put(SignalHead.LUNAR, getDefaultNumberForAppearance(SignalHead.LUNAR)); 202 appearanceToOutput.put(SignalHead.FLASHRED, getDefaultNumberForAppearance(SignalHead.FLASHRED)); 203 appearanceToOutput.put(SignalHead.FLASHYELLOW, getDefaultNumberForAppearance(SignalHead.FLASHYELLOW)); 204 appearanceToOutput.put(SignalHead.FLASHGREEN, getDefaultNumberForAppearance(SignalHead.FLASHGREEN)); 205 appearanceToOutput.put(SignalHead.FLASHLUNAR, getDefaultNumberForAppearance(SignalHead.FLASHLUNAR)); 206 appearanceToOutput.put(SignalHead.DARK, getDefaultNumberForAppearance(SignalHead.DARK)); 207 } 208 209 public static int getDefaultNumberForAppearance(int i) { 210 switch (i) { 211 case SignalHead.DARK: 212 return 8; 213 case SignalHead.RED: 214 return 0; 215 case SignalHead.YELLOW: 216 return 1; 217 case SignalHead.GREEN: 218 return 2; 219 case SignalHead.LUNAR: 220 return 3; 221 case SignalHead.FLASHRED: 222 return 4; 223 case SignalHead.FLASHYELLOW: 224 return 5; 225 case SignalHead.FLASHGREEN: 226 return 6; 227 case SignalHead.FLASHLUNAR: 228 return 7; 229 default: 230 return 8; 231 } 232 } 233 234 private int packetSendCount = 3; 235 /** 236 * Set Number of times the packet should be sent to the track. 237 * @param count - less than 1 is treated as 1 238 */ 239 public void setDccSignalHeadPacketSendCount(int count) { 240 if (count > 0) { 241 packetSendCount = count; 242 } else { 243 packetSendCount = 1; 244 } 245 } 246 247 /** 248 * Get the number of times the packet should be sent to the track. 249 * 250 * @return the count 251 */ 252 public int getDccSignalHeadPacketSendCount() { 253 return packetSendCount; 254 } 255 256 private int dccSignalDecoderAddress; 257 258 @Override 259 boolean isTurnoutUsed(Turnout t) { 260 return false; 261 } 262 263 private final static Logger log = LoggerFactory.getLogger(DccSignalHead.class); 264 265}