001package jmri.jmrix.lenz;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006/**
007 * Defines the standard/common routines used in multiple classes related to the
008 * a Lenz Command Station, on an XpressNet network.
009 *
010 * @author Bob Jacobsen Copyright (C) 2001 Portions by Paul Bender Copyright (C) 2003
011 */
012public class LenzCommandStation implements jmri.CommandStation {
013
014    /* The First group of routines is for obtaining the Software and
015     hardware version of the Command station */
016
017    /**
018     * We need to add a few data members for saving the version information we
019     * get from the layout.
020     */
021    private int cmdStationType = -1;
022    private float cmdStationSoftwareVersion = -1;
023    private int cmdStationSoftwareVersionBCD = -1;
024
025    /**
026     * Return the CS Type.
027     * @return CS type.
028     */
029    public int getCommandStationType() {
030        return cmdStationType;
031    }
032
033    /**
034     * Set the CS Type.
035     * @param t CS type.
036     */
037    public void setCommandStationType(int t) {
038        cmdStationType = t;
039    }
040
041    /**
042     * Set the CS Type based on an XpressNet Message.
043     * @param l XNetReply containing the CS type.
044     */
045    public void setCommandStationType(XNetReply l) {
046        if (l.getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE) {
047            // This is the Command Station Software Version Response
048            if (l.getElement(1) == XNetConstants.CS_SOFTWARE_VERSION) {
049                cmdStationType = l.getElement(3);
050            }
051        }
052    }
053
054    /**
055     * Get the CS Software Version.
056     * @return software version.
057     */
058    public float getCommandStationSoftwareVersion() {
059        return cmdStationSoftwareVersion;
060    }
061
062    /**
063     * Get the CS Software Version in BCD (for use in comparisons).
064     * @return software version.
065     */
066    public float getCommandStationSoftwareVersionBCD() {
067        return cmdStationSoftwareVersionBCD;
068    }
069
070    /**
071     * Set the CS Software Version.
072     * @param v software version.
073     */
074    public void setCommandStationSoftwareVersion(float v) {
075        cmdStationSoftwareVersion = v;
076    }
077
078    /**
079     * Set the CS Software Version based on an XpressNet Message.
080     * @param l reply containing CS version.
081     */
082    public void setCommandStationSoftwareVersion(XNetReply l) {
083        if (l.getElement(0) == XNetConstants.CS_SERVICE_MODE_RESPONSE) {
084            // This is the Command Station Software Version Response
085            if (l.getElement(1) == XNetConstants.CS_SOFTWARE_VERSION) {
086                try {
087                    cmdStationSoftwareVersion = (l.getElementBCD(2).floatValue()) / 10;
088                } catch (java.lang.NumberFormatException nfe) {
089                    // the number was not in BCD format as expected.
090                    // the upper nibble is the major version and the lower 
091                    // nibble is the minor version.
092                    cmdStationSoftwareVersion = ((l.getElement(2) & 0xf0) >> 4) + (l.getElement(2) & 0x0f) / 100.0f;
093                }
094                cmdStationSoftwareVersionBCD = l.getElement(2);
095            }
096        }
097    }
098
099    /**
100     * Provide the version string returned during the initial check.
101     * @return human readable version string.
102     */
103    public String getVersionString() {
104        return Bundle.getMessage("CSVersionString", getCommandStationType(),getCommandStationSoftwareVersionBCD());
105    }
106
107    /**
108     * XpressNet command station does provide Ops Mode.
109     *
110     * @return true if CS type 1 or 2, else false.
111     */
112    public boolean isOpsModePossible() {
113        return cmdStationType != 0x01 && cmdStationType != 0x02;
114    }
115
116    // A few utility functions
117
118    /**
119     * Get the Lower byte of a locomotive address from the decimal locomotive
120     * address.
121     * @param address loco address.
122     * @return low address byte including DCC offset.
123     */
124    public static int getDCCAddressLow(int address) {
125        /* For addresses below 100, we just return the address, otherwise,
126         we need to return the upper byte of the address after we add the
127         offset 0xC000. The first address used for addresses over 99 is 0xC064*/
128        if (address < 100) {
129            return (address);
130        } else {
131            int temp = address + 0xC000;
132            temp = temp & 0x00FF;
133            return temp;
134        }
135    }
136
137    /**
138     * Get the Upper byte of a locomotive address from the decimal locomotive
139     * address.
140     * @param address loco address.
141     * @return upper byte after DCC offset.
142     */
143    public static int getDCCAddressHigh(int address) {
144        /* this isn't actually the high byte, For addresses below 100, we
145         just return 0, otherwise, we need to return the upper byte of the
146         address after we add the offset 0xC000 The first address used for
147         addresses over 99 is 0xC064*/
148        if (address < 100) {
149            return (0x00);
150        } else {
151            int temp = address + 0xC000;
152            temp = temp & 0xFF00;
153            temp = temp / 256;
154            return temp;
155        }
156    }
157
158    /**
159     * We need to calculate the locomotive address when doing the translations
160     * back to text. XpressNet Messages will have these as two elements, which
161     * need to get translated back into a single address by reversing the
162     * formulas used to calculate them in the first place.
163     *
164     * @param AH the high order byte of the address
165     * @param AL the low order byte of the address
166     * @return the address as an integer.
167     */
168    static public int calcLocoAddress(int AH, int AL) {
169        if (AH == 0x00) {
170            /* if AH is 0, this is a short address */
171            return (AL);
172        } 
173        
174        if ((AH & 0xC0) == 0x80) {
175            /* this is accessory address */
176            if ((AL & 0x80) == 0x80) {
177                /* this is Basic accessory decoder */
178                int address;
179                
180                int part1 ;
181                part1 = (AH & 0x3F) ;
182                
183                int part2 ;
184                part2 = ~AL ;
185                part2 = (part2 & 0x70) ;
186                part2 = part2 << 2 ;
187                
188                address = part2 | part1 ;
189                return (address) ;
190            } else {
191                /* this is Extended accessory decoder */
192                int address;
193                
194                int part1 ;
195                part1 = (AH & 0x3F) ;
196                // part1 = part1 + 1 ;
197                part1 = part1 << 2 ;
198                
199                int part2 ;
200                part2 = ~AL ;
201                part2 = (part2 & 0x70) ;
202                part2 = part2 << 4 ;
203                
204                int part3 ;
205                part3 = AL ;
206                part3 = (part3 & 0x06) ;
207                part3 = part3 >> 1 ;
208                
209                address = part2 | part1 | part3;
210                address = address + 1;
211                
212                return (address) ;
213            }
214        }
215        
216        /* This must be a long locomotive address */
217        int address;
218        address = ((AH * 256) & 0xFF00);
219        address += (AL & 0xFF);
220        address -= 0xC000;
221        return (address);
222    }
223
224    /* To Implement the CommandStation Interface, we have to define the 
225     sendPacket function */
226
227    /**
228     * Send a specific packet to the rails.
229     *
230     * @param packet  Byte array representing the packet, including the
231     *                error-correction byte. Must not be null.
232     * @param repeats Number of times to repeat the transmission.
233     */
234    @Override
235    public boolean sendPacket(byte[] packet, int repeats) {
236
237        if (_tc == null) {
238            log.error("Send Packet Called without setting traffic controller");
239            return false;
240        }
241
242        XNetMessage msg = XNetMessage.getNMRAXNetMsg(packet);
243        for (int i = 0; i < repeats; i++) {
244            _tc.sendXNetMessage(msg, null);
245        }
246        return true;
247    }
248
249    /*
250     * For the command station interface, we need to set the traffic 
251     * controller.
252     */
253    public void setTrafficController(XNetTrafficController tc) {
254        _tc = tc;
255    }
256
257    private XNetTrafficController _tc = null;
258
259    public void setSystemConnectionMemo(XNetSystemConnectionMemo memo) {
260        adaptermemo = memo;
261    }
262
263    XNetSystemConnectionMemo adaptermemo;
264
265    @Override
266    public String getUserName() {
267        if (adaptermemo == null) {
268            return Bundle.getMessage("MenuXpressNet");
269        }
270        return adaptermemo.getUserName();
271    }
272
273    @Override
274    public String getSystemPrefix() {
275        if (adaptermemo == null) {
276            return "X";
277        }
278        return adaptermemo.getSystemPrefix();
279    }
280
281    /*
282     * Register for logging.
283     */
284    private static final Logger log = LoggerFactory.getLogger(LenzCommandStation.class);
285
286}