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}