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}