001package jmri.jmrix.can.adapters.gridconnect;
002
003import jmri.jmrix.AbstractMRReply;
004import jmri.jmrix.can.CanReply;
005import org.slf4j.Logger;
006import org.slf4j.LoggerFactory;
007
008/**
009 * Class for replies in a GridConnect based message/reply protocol.
010 * <p>
011 * The GridConnect protocol encodes messages as an ASCII string of up to 24
012 * characters of the form: :ShhhhNd0d1d2d3d4d5d6d7;
013 * <p>
014 * hhhh is the two byte (11
015 * useful bits) header The S indicates a standard CAN frame
016 * :XhhhhhhhhNd0d1d2d3d4d5d6d7; The X indicates an extended CAN frame N or R
017 * indicates a normal or remote frame, in position 6 or 10 d0 - d7 are the (up
018 * to) 8 data bytes
019 * <p>
020 *
021 * @author Andrew Crosland Copyright (C) 2008, 2009
022 * @author Bob Jacobsen Copyright (C) 2008
023 */
024public class GridConnectReply extends AbstractMRReply {
025
026    static final int MAXLEN = 27;
027
028    /**
029     * Creates a new instance of GridConnectReply.
030     */
031    public GridConnectReply() {
032        _nDataChars = 0;
033        _dataChars = new int[MAXLEN];
034    }
035
036    /**
037     * Creates a new GridConnectReply from String.
038     * @param s String to use as basis for the GCReply.
039     */
040    public GridConnectReply(String s) {
041        _nDataChars = s.length();
042        for (int i = 0; i < s.length(); i++) {
043            _dataChars[i] = s.charAt(i);
044        }
045    }
046
047    /**
048     * Create a CanReply from a GridConnectReply.
049     * @return new CanReply Outgoing message.
050     */
051    public CanReply createReply() {
052        CanReply ret = new CanReply();
053
054        log.debug("createReply converts from {}", this);
055
056        // basic checks drop out the frame
057        if (!basicFormatCheck()) {
058            ret.setHeader(0);
059            ret.setNumDataElements(0);
060            return ret;
061        }
062
063        // Is it an Extended frame?
064        if (isExtended()) {
065            ret.setExtended(true);
066        }
067
068        // Copy the header
069        ret.setHeader(getHeader());
070
071        // Is it an RTR frame?
072        if (isRtr()) {
073            ret.setRtr(true);
074        }
075
076        // Get the data
077        for (int i = 0; i < getNumBytes(); i++) {
078            ret.setElement(i, getByte(i));
079        }
080        ret.setNumDataElements(getNumBytes());
081        log.debug("createReply converted to {}", ret);
082        return ret;
083    }
084
085    /**
086     * Check if this GCReply contains an Extended or Standard flag.
087     * @return true if contains a flag, else false.
088     */
089    protected boolean basicFormatCheck() {
090        return !((getElement(1) != 'X') && (getElement(1) != 'S'));
091    }
092
093    /**
094     * {@inheritDoc}
095     */
096    @Override
097    protected int skipPrefix(int index) {
098        while (_dataChars[index] == ':') {
099            index++;
100        }
101        return index;
102    }
103
104    /**
105     * {@inheritDoc}
106     */
107    @Override
108    public int getNumDataElements() {
109        return _nDataChars;
110    }
111
112    /**
113     * Set Number of Data Elements.
114     * Max. length set by the MAXLEN constant.
115     * @param n Number Elements.
116     */
117    public void setNumDataElements(int n) {
118        _nDataChars = (n <= MAXLEN) ? n : MAXLEN;
119    }
120
121    /**
122     * {@inheritDoc}
123     */
124    @Override
125    public int getElement(int n) {
126        return _dataChars[n];
127    }
128
129    /**
130     * {@inheritDoc}
131     */
132    @Override
133    public void setElement(int n, int v) {
134        if (n < MAXLEN) {
135            _dataChars[n] = v;
136            _nDataChars = Math.max(_nDataChars, n + 1);
137        }
138    }
139
140    /**
141     * Get if the GridConnectReply is Extended.
142     * @return true if extended, else false.
143     */
144    public boolean isExtended() {
145        return (getElement(1) == 'X');
146    }
147
148    /**
149     * Get if the GridConnectReply is RtR.
150     * @return true if RtR, else false.
151     */
152    public boolean isRtr() {
153        return (getElement(_RTRoffset) == 'R');
154    }
155
156    /**
157     * {@inheritDoc}
158     */
159    @Override
160    public int maxSize() {
161        return MAXLEN;
162    }
163
164    /**
165     * Set the GridConnectReply data by Array.
166     * @param d data array.
167     */
168    public void setData(int[] d) {
169        int len = (d.length <= MAXLEN) ? d.length : MAXLEN;
170        for (int i = 0; i < len; i++) {
171            _dataChars[i] = d[i];
172        }
173    }
174
175    // pointer to the N or R character
176    int _RTRoffset = -1;
177
178    /**
179     * Get the CAN header by using chars from 2 to up to 9.
180     * <p>
181     * Right justify standard headers that had 4 digits.
182     *
183     * @return the CAN header as an int
184     */
185    public int getHeader() {
186        int val = 0;
187        for (int i = 2; i <= 10; i++) {
188            _RTRoffset = i;
189            if (_dataChars[i] == 'N') {
190                break;
191            }
192            if (_dataChars[i] == 'R') {
193                break;
194            }
195            val = val * 16 + getHexDigit(i);
196        }
197        return val;
198    }
199
200    /**
201     * Get the number of data bytes.
202     * @return number of bytes in reply.
203     */
204    public int getNumBytes() {
205        // subtract framing and ID bytes, etc and each byte is two ASCII hex digits
206        return (_nDataChars - (_RTRoffset + 1)) / 2;
207    }
208
209    /**
210     * Get a hex data byte from the message.
211     * <p>
212     * Data bytes are encoded as two ASCII hex digits starting at byte 7 of the
213     * message.
214     *
215     * @param b The byte offset (0 - 7)
216     * @return The value
217     */
218    public int getByte(int b) {
219        if ((b >= 0) && (b <= 7)) {
220            int index = b * 2 + _RTRoffset + 1;
221            int hi = getHexDigit(index++);
222            int lo = getHexDigit(index);
223            if ((hi < 16) && (lo < 16)) {
224                return (hi * 16 + lo);
225            }
226        }
227        return 0;
228    }
229
230    // Get a single hex digit. returns 0 if digit is invalid
231    private int getHexDigit(int index) {
232        int b = 0;
233        b = _dataChars[index];
234        if ((b >= '0') && (b <= '9')) {
235            b = b - '0';
236        } else if ((b >= 'A') && (b <= 'F')) {
237            b = b - 'A' + 10;
238        } else if ((b >= 'a') && (b <= 'f')) {
239            b = b - 'a' + 10;
240        } else {
241            b = 0;
242        }
243        return (byte) b;
244    }
245
246    private final static Logger log = LoggerFactory.getLogger(GridConnectReply.class);
247}
248
249