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