001package jmri.jmrix.loconet;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006/**
007 * Utilities for handling LocoNet sensor addresses.
008 * <p>
009 * There are three addressing spaces for LocoNet sensors:
010 * <ul>
011 *   <li>The space used for DS54 inputs, where the least-significant-bit in the
012 *   address refers to the "Aux" and "Switch" inputs. These are represented by
013 *   system names of the form LSnnnA and LSnnnS respectively. nnn is then the
014 *   turnout number of the DS54 channel.
015 *   <li>The space used for BDL16 inputs, where the card and section numbers are
016 *   part of the address. These are represented by names of the form LScccA1
017 *   through LScccA4, LScccB1 through LScccB4, and on through LScccD4. ccc is the
018 *   BDL16 card number.
019 *   <li>A straight-forward numeric space, represented by LSmmm. Note that this is
020 *   a 1-4096 scheme, not a 0-4095.
021 * </ul>
022 * Some of the message formats used in this class are Copyright Digitrax, Inc.
023 * and used with permission as part of the JMRI project. That permission does
024 * not extend to uses in other software products. If you wish to use this code,
025 * algorithm or these message formats outside of JMRI, please contact Digitrax
026 * Inc for separate permission.
027 *
028 * @author Bob Jacobsen Copyright (C) 2001, 2002
029 */
030public class LnSensorAddress {
031
032    int _low;
033    int _high;
034    int _as;
035    String prefix;
036
037    boolean _valid;
038
039    public LnSensorAddress(int sw1, int sw2, String prefix) {
040        _as = sw2 & 0x20;  // should be a LocoNet constant?
041        _high = sw2 & 0x0F;
042        _low = sw1 & 0x7F;
043        _valid = true;
044        this.prefix = prefix;
045    }
046
047    public LnSensorAddress(String s, String prefix) {
048        _valid = false;
049        this.prefix = prefix;
050
051        // check valid
052        if (s.startsWith(prefix + "S")) {
053            // parse out and decode the name
054            if (s.charAt(s.length() - 1) == 'A') {
055                // DS54 addressing, Aux input
056                _as = 0x20;
057                int n = Integer.parseInt(s.substring(prefix.length() + 1, s.length() - 1));
058                _high = n / 128;
059                _low = n & 0x7F;
060                _valid = true;
061            } else if (s.charAt(s.length() - 1) == 'S') {
062                // DS54 addressing, Switch input
063                _as = 0x00;
064                int n = Integer.parseInt(s.substring(prefix.length() + 1, s.length() - 1));
065                _high = n / 128;
066                _low = n & 0x7F;
067                _valid = true;
068            } else {
069                // BDL16?
070                char c = s.charAt(s.length() - 2);
071                if (c >= 'A' && c <= 'D') {
072                    // BDL16 addressing
073                    int d = 0;
074                    switch (c) {
075                        case 'A':
076                            d = 0;
077                            break;
078                        case 'B':
079                            d = 1;
080                            break;
081                        case 'C':
082                            d = 2;
083                            break;
084                        case 'D':
085                            d = 3;
086                            break;
087                        default:
088                            log.warn("Unhandled addr code: {}", c);
089                            break;
090                    }
091                    int n = Integer.parseInt(s.substring(prefix.length() + 1, s.length() - 2)) * 16 + d * 4
092                            + Integer.parseInt(s.substring(s.length() - 1, s.length()));
093                    _high = n / 128;
094                    _low = (n & 0x7F) / 2;
095                    _as = (n & 0x01) * 0x20;
096                    _valid = true;
097                } else {
098                    // assume that its LSnnn style
099                    int n = Integer.parseInt(s.substring(prefix.length() + 1, s.length())) - 1;
100                    _high = n / 256;
101                    _low = (n & 0xFE) / 2;
102                    _as = (n & 0x01) * 0x20;
103                    _valid = true;
104                }
105            }
106        } else {
107            // didn't find a leading LS, complain
108            reportParseError(s);
109        }
110    }
111
112    void reportParseError(String s) {
113        log.error("Can't parse sensor address string: {}", s);
114    }
115
116    /**
117     * Update a LocoNet message to have this address.
118     * 
119     * It is assumed that the sensor address may be encoded into bytes 1 and 2 of the 
120     * message.
121     *
122     * @param m a LocoNetmessage to be updated to contain this object's sensor address
123     */
124    public void insertAddress(LocoNetMessage m) {
125        m.setElement(1, getLowBits());
126        m.setElement(2, getHighBits() | getASBit());
127    }
128
129    // convenient calculations
130    public boolean matchAddress(int a1, int a2) { // a1 is byte 1 of ln msg, a2 is byte 2
131        if (getHighBits() != (a2 & 0x0f)) {
132            return false;
133        }
134        if (getLowBits() != (a1 & 0x7f)) {
135            return false;
136        }
137        if (getASBit() != (a2 & 0x20)) {
138            return false;
139        }
140        return true;
141    }
142
143    /**
144     * @return integer value of this address in 0-4095 space
145     */
146    protected int asInt() {
147        return _high * 256 + _low * 2 + (_as != 0 ? 1 : 0);
148    }
149
150    // accessors for parsed data
151    public int getLowBits() {
152        return _low;
153    }
154
155    public int getHighBits() {
156        return _high;
157    }
158
159    /**
160     * The bit representing the Aux or Sensor input
161     *
162     * @return 0x20 for aux input, 0x00 for switch input
163     */
164    public int getASBit() {
165        return _as;
166    }
167
168    public boolean isValid() {
169        return _valid;
170    }
171
172    @Override
173    public String toString() {
174        return getNumericAddress() + ":"
175                + getDS54Address() + ":"
176                + getBDL16Address();
177    }
178
179    /**
180     * Name in the 1-4096 space
181     *
182     * @return LSnnn
183     */
184    public String getNumericAddress() {
185        return prefix + "S" + (asInt() + 1);
186    }
187
188    /**
189     * Name in the DS54 space
190     *
191     * @return LSnnnA or LSnnnS, depending on Aux or Switch input
192     */
193    public String getDS54Address() {
194        if (_as != 0) {
195            return prefix + "S" + (_high * 128 + _low) + "A";
196        } else {
197            return prefix + "S" + (_high * 128 + _low) + "S";
198        }
199    }
200
201    /**
202     * Name in the BDL16 space
203     *
204     * @return e.g. LSnnnA3, with nnn the BDL16 number, A the section number,
205     *         and 3 the channel number
206     */
207    public String getBDL16Address() {
208        String letter = null;
209        String digit = null;
210
211        switch (asInt() & 0x03) {
212            case 0:
213                digit = "0";
214                break;
215            case 1:
216                digit = "1";
217                break;
218            case 2:
219                digit = "2";
220                break;
221            case 3:
222                digit = "3";
223                break;
224            default:
225                digit = "X";
226                log.error("Unexpected digit value: {}", asInt());
227        }
228        switch ((asInt() & 0x0C) / 4) {
229            case 0:
230                letter = "A";
231                break;
232            case 1:
233                letter = "B";
234                break;
235            case 2:
236                letter = "C";
237                break;
238            case 3:
239                letter = "D";
240                break;
241            default:
242                letter = "X";
243                log.error("Unexpected letter value: {}", asInt());
244        }
245        return prefix + "S" + (asInt() / 16) + letter + digit;
246    }
247
248    private final static Logger log = LoggerFactory.getLogger(LnSensorAddress.class);
249
250}