001package jmri.jmrix.sprog;
002
003import jmri.jmrix.AbstractMRReply;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007/**
008 * Carries the reply to a SprogMessage.
009 *
010 * @author Bob Jacobsen Copyright (C) 2001
011 * @author Andrew Berridge - refactored, cleaned up, Feb 2010
012 */
013public class SprogReply extends AbstractMRReply {
014
015    // Longest boot reply is 256bytes each preceded by DLE + 2xSTX + ETX
016    static public final int maxSize = 515;
017    private boolean _isBoot = false;
018    protected int _id = -1;
019
020    // create a new one
021    public SprogReply() {
022        super();
023    }
024
025    public void setId(int id) {
026        _id = id;
027    }
028    
029    public int getId() {
030        return _id;
031    }
032    
033    // no need to do anything
034    @Override
035    protected int skipPrefix(int index) {
036        return index;
037    }
038
039    /**
040     * Create a new SprogReply as a deep copy of an existing SprogReply.
041     *
042     * @param m the SprogReply to copy
043     */
044    public SprogReply(SprogReply m) {
045        this();
046        if (m == null) {
047            log.error("copy ctor of null message");
048            return;
049        }
050        _nDataChars = m._nDataChars;
051        _isBoot = m._isBoot;
052        if (m.isUnsolicited()) {
053            super.setUnsolicited();
054        }
055        for (int i = 0; i < _nDataChars; i++) {
056            _dataChars[i] = m._dataChars[i];
057        }
058        _id = m._id;
059    }
060
061    /**
062     * Create a SprogReply from a String.
063     *
064     * @param replyString a String containing the contents of the reply
065     * @param isBoot a boolean indicating if this is a boot reply
066     */
067    public SprogReply(String replyString, boolean isBoot) {
068        this(replyString);
069        _isBoot = isBoot;
070    }
071
072    public SprogReply(String replyString) {
073        super(replyString);
074    }
075
076    /**
077     * Is this reply indicating that an overload condition was detected?
078     * 
079     * @return boolean true for overload
080     */
081    public boolean isOverload() {
082        return (this.toString().contains("!O"));
083    }
084
085    /**
086     * Is this reply indicating that a general error has occurred?
087     *
088     * @return boolean true for error message
089     */
090    public boolean isError() {
091        return (this.toString().contains("!E"));
092    }
093
094    /**
095     * Check and strip framing characters and DLE from a SPROG bootloader reply.
096     * 
097     * @return boolean result of message validation
098     */
099    public boolean strip() {
100        char tmp[] = new char[_nDataChars];
101        int j = 0;
102        _isBoot = true; // definitely a boot message
103        // Check framing characters
104        if (_dataChars[0] != SprogMessage.STX) {
105            return false;
106        }
107        if (_dataChars[1] != SprogMessage.STX) {
108            return false;
109        }
110        if (_dataChars[_nDataChars - 1] != SprogMessage.ETX) {
111            return false;
112        }
113
114        // Ignore framing characters and strip DLEs
115        for (int i = 2; i < _nDataChars - 1; i++) {
116            if (_dataChars[i] == SprogMessage.DLE) {
117                i++;
118            }
119            tmp[j++] = (char) _dataChars[i];
120        }
121
122        // Copy back to original SprogReply
123        for (int i = 0; i < j; i++) {
124            _dataChars[i] = tmp[i];
125        }
126        _nDataChars = j;
127        return true;
128    }
129
130    /**
131     * Check and strip checksum from a SPROG bootloader reply.
132     * <p>
133     * Assumes framing and DLE chars have been stripped
134     * 
135     * @return boolean result of checksum validation
136     */
137    public boolean getChecksum() {
138        int checksum = 0;
139        for (int i = 0; i < _nDataChars; i++) {
140            checksum += _dataChars[i] & 0xff;
141        }
142        _nDataChars--;
143        return ((checksum & 0xff) == 0);
144    }
145
146    /**
147     * Return a string representation of this SprogReply.
148     * 
149     * @return String The string representation
150     */
151    @Override
152    public String toString() {
153        //String s = "";
154        StringBuffer buf = new StringBuffer();
155        if (_isBoot || (_dataChars[0] == SprogMessage.STX)) {
156            for (int i = 0; i < _nDataChars; i++) {
157                //s+="<"+(((char)_dataChars[i]) & 0xff)+">";
158                buf.append("<");
159                buf.append(_dataChars[i]);
160                buf.append(">");
161            }
162        } else {
163            for (int i = 0; i < _nDataChars; i++) {
164                //s+=;
165                buf.append((char) _dataChars[i]);
166            }
167        }
168        return buf.toString();
169    }
170
171    /**
172     * Extract Read-CV returned value from a message.
173     * <p>
174     * SPROG is assumed to not be echoing commands. A reply to a command may
175     * include the prompt that was printed after the previous command.
176     * <p>
177     * Reply to a CV read is of the form " = hvv" where vv is the CV value in hex
178     *
179     * @return -1 if message can't be parsed
180     */
181    @Override
182    public int value() {
183        int index = 0;
184        index = skipWhiteSpace(index);
185        index = skipEqual(index);
186        index = skipWhiteSpace(index);
187        String s1 = "" + (char) getElement(index);
188        String s2 = "" + (char) getElement(index + 1);
189        int val = -1;
190        try {
191            int sum = Integer.valueOf(s2, 16);
192            sum += 16 * Integer.valueOf(s1, 16);
193            val = sum;  // don't do this assign until now in case the conversion throws
194        } catch (NumberFormatException e) {
195            log.error("Unable to get number from reply: \"{}{}\" index: {} message: \"{}\"", s1, s2, index, toString());
196        }
197        return val;
198    }
199
200    /**
201     * Find a specific string in the reply.
202     *
203     * @param s string to look for
204     * @return index of String s in the reply
205     */
206    @Override
207    public int match(String s) {
208        String rep = new String(_dataChars, 0, _nDataChars);
209        return rep.indexOf(s);
210    }
211
212    private int skipEqual(int index) {
213        // start at index, skip over the equals and hex prefix
214        int len = "= h".length();
215        if (getNumDataElements() >= index + len - 1
216                && '=' == (char) getElement(index)
217                && ' ' == (char) getElement(index + 1)
218                && 'h' == (char) getElement(index + 2)) {
219            index += len;
220        }
221        return index;
222    }
223
224    /**
225     * Normal SPROG replies will end with the prompt for the next command.
226     * 
227     * @return true if end of normal reply is found
228     */
229    public boolean endNormalReply() {
230        // Detect that the reply buffer ends with "P> " or "R> " (note ending space)
231        int num = this.getNumDataElements();
232        if (num >= 3) {
233            // ptr is offset of last element in SprogReply
234            int ptr = num - 1;
235            if (this.getElement(ptr) != ' ') {
236                return false;
237            }
238            if (this.getElement(ptr - 1) != '>') {
239                return false;
240            }
241            if ((this.getElement(ptr - 2) != 'P') && (this.getElement(ptr - 2) != 'R')) {
242                return false;
243            }
244            // Now see if it's unsolicited !O for overload
245            if (num >= 5) {
246                for (int i = 0; i < num - 1; i++) {
247                    if ((this.getElement(i) == '!')) {
248                        super.setUnsolicited();
249                    }
250                }
251            }
252            return true;
253        } else {
254            return false;
255        }
256    }
257
258    /**
259     * Bootloader will end with ETX with no preceding DLE.
260     * 
261     * @return true if end of bootloader reply is found
262     */
263    public boolean endBootReply() {
264        // Detect that the reply buffer ends with ETX with no preceding DLE.
265        // This is the end of a SPROG II bootloader reply or the end of
266        // a SPROG v4 echoing the bootloader version request
267        int num = this.getNumDataElements();
268        if (num >= 2) {
269            // ptr is offset of last element in SprogReply
270            int ptr = num - 1;
271            if ((this.getElement(ptr) & 0xff) != SprogMessage.ETX) {
272                return false;
273            }
274            if ((this.getElement(ptr - 1) & 0xff) == SprogMessage.DLE) {
275                return false;
276            }
277            return true;
278        } else {
279            return false;
280        }
281    }
282
283    private final static Logger log = LoggerFactory.getLogger(SprogReply.class);
284
285}