001package jmri.jmrix.grapevine;
002
003import jmri.util.StringUtil;
004import org.slf4j.Logger;
005import org.slf4j.LoggerFactory;
006
007/**
008 * Contains the data payload of a serial packet. Note that it's _only_ the
009 * payload.
010 * <p>
011 * See the Grapevine <a href="package-summary.html">Binary Message Format Summary</a>
012 *
013 * @author Bob Jacobsen Copyright (C) 2001, 2003, 2006, 2007, 2008
014 * @author Egbert Broerse Copyright (C) 2018
015 */
016public class SerialMessage extends jmri.jmrix.AbstractMRMessage {
017
018    /**
019     * Create a new SerialMessage instance.
020     */
021    public SerialMessage() {
022        super(4); // most Grapevine messages are four bytes, binary
023        setBinary(true);
024    }
025
026    /**
027     * Create a new SerialMessage instance of a given byte size.
028     *
029     * @param len number of elements in the message
030     */
031    public SerialMessage(int len) {
032        super(len); // most Grapevine messages are four bytes, binary
033        setBinary(true);
034    }
035
036    /**
037     * Copy a SerialMessage to a new instance.
038     *
039     * @param m the message to copy
040     */
041    public SerialMessage(SerialMessage m) {
042        super(m);
043        setBinary(true);
044    }
045
046    /**
047     * Create a new Message instance from a string.
048     * Interprets the String as the exact sequence to send,
049     * byte-for-byte.
050     *
051     * @param m String to use as message content
052     */
053    public SerialMessage(String m) {
054        super(m);
055        setBinary(true);
056    }
057
058    /**
059     * Interpret the byte array as a sequence of characters to send.
060     * @deprecated 5.13.5, unused, requires further development.
061     * @param a Array of bytes to send
062     */
063    @Deprecated( since="5.13.5", forRemoval=true)
064    public SerialMessage(byte[] a) {
065        // super(String.valueOf(a)); // Spotbug toString on array
066        // requires further development to produce correct values for hardware type.
067        super(StringUtil.hexStringFromBytes(a).replaceAll("\\s", ""));
068        setBinary(true);
069    }
070
071    // no replies expected, don't wait for them
072    @Override
073    public boolean replyExpected() {
074        return false;
075    }
076
077    // static methods to recognize a message
078
079    public int getAddr() {
080        return getElement(0) & 0x7F;
081    }
082
083    // static methods to return a formatted message
084
085    /**
086     * For Grapevine, which doesn't have a data poll, the poll operation is only
087     * used to see that the nodes are present.
088     * This is done by sending a "get software version" command.
089     * @param addr address to poll.
090     * @return serial message to poll data.
091     */
092    static public SerialMessage getPoll(int addr) {
093        // eventually this will have to include logic for reading 
094        // various bytes on the card, but our supported 
095        // cards don't require that yet
096        SerialMessage m = new SerialMessage();
097        m.setElement(0, addr | 0x80);
098        m.setElement(1, 119);  // get software version
099        m.setElement(2, addr | 0x80);  // read first two bytes
100        m.setElement(3, 119);  // send twice, without parity
101        m.setReplyLen(2);      // only two bytes come back
102        return m;
103    }
104
105    public void setBank(int b) {
106        if ((b > 7) || (b < 0)) {
107            log.error("Setting back to bad value: {}", b);
108        }
109        int old = getElement(3) & 0xF;
110        setElement(3, old | ((b & 0x7) << 4));
111    }
112
113    public void setParity() {
114        setParity(0);
115    }
116
117    public void setParity(int start) {
118        // leave unchanged if poll
119        if ((getElement(1 + start) == 119) && (getElement(3 + start) == 119)) {
120            return;
121        }
122        // error messages have zero parity
123        if ((getElement(0 + start) & 0x7F) == 0) {
124            setElement(3, getElement(3 + start) & 0xF0);
125            return;
126        }
127        // nibble sum method
128        int sum = getElement(0 + start) & 0x0F;
129        sum += (getElement(0 + start) & 0x70) >> 4;
130        sum += (getElement(1 + start) * 2) & 0x0F;
131        sum += ((getElement(1 + start) * 2) & 0xF0) >> 4;
132        sum += (getElement(3 + start) & 0x70) >> 4;
133
134        int parity = 16 - (sum & 0xF);
135
136        setElement(3 + start, (getElement(3 + start) & 0xF0) | (parity & 0xF));
137    }
138
139    // default to expecting four reply characters, a standard message
140    int replyLen = 4;
141
142    /**
143     * Set the number of characters expected back from the command station.
144     * Normally four, this is used to set other lengths for special cases, like
145     * a reply to a poll (software version) message.
146     * @param len reply length.
147     */
148    public void setReplyLen(int len) {
149        replyLen = len;
150    }
151
152    public int getReplyLen() {
153        return replyLen;
154    }
155
156    /**
157     * Format the reply as human-readable text.
158     * @return human-readable text of reply.
159     */
160    public String format() {
161        if (getNumDataElements() == 8) {
162            String result = "(2-part) ";
163            result += staticFormat(getElement(0) & 0xff, getElement(1) & 0xff, getElement(2) & 0xff, getElement(3) & 0xff);
164            result += "; ";
165            result += staticFormat(getElement(4) & 0xff, getElement(5) & 0xff, getElement(6) & 0xff, getElement(7) & 0xff);
166            return result;
167        } else {
168            return staticFormat(getElement(0) & 0xff, getElement(1) & 0xff, getElement(2) & 0xff, getElement(3) & 0xff);
169        }
170    }
171
172    /**
173     * Provide a human-readable form of a message.
174     * <p>
175     * Used by both SerialMessage and SerialReply, because so much of it is
176     * common. That forces the passing of arguments as numbers. Short messages
177     * are marked by having missing bytes put to -1 in the arguments.
178     * See the Grapevine <a href="package-summary.html">Binary Message Format Summary</a>
179     * @param b1 1st message byte
180     * @param b2 2nd message byte
181     * @param b3 3rd message byte
182     * @param b4 4th message byte
183     * @return Human-readable form
184     */
185    static String staticFormat(int b1, int b2, int b3, int b4) {
186        String result;
187
188        // short reply is special case
189        if (b3 < 0) {
190            return "Node " + (b1 & 0x7F) + " reports software version " + b2;
191        }
192        // address == 0 is a special case
193        if ((b1 & 0x7F) == 0) {
194            // error report
195            result = "Error report from node " + b2 + ": ";
196            switch (((b4 & 0x70) >> 4) - 1) {  // the -1 is an observed offset
197                case 0:
198                    result += "Parity Error";
199                    break;
200                case 1:
201                    result += "First Byte Data";
202                    break;
203                case 2:
204                    result += "Second Byte Address";
205                    break;
206                case 3:
207                    result += "error 3";
208                    break;
209                case 4:
210                    result += "Software UART Overflow";
211                    break;
212                case 5:
213                    result += "Serial Detector Power Failure";
214                    break;
215                case 6:
216                    result += "Printer Busy";
217                    break;
218                case 7:
219                    result += "I/O Configuration Not Set";
220                    break;
221                default:
222                    result += "error number " + ((b4 & 0x70) >> 4);
223                    break;
224            }
225            return result;
226        }
227
228        // normal message
229        result = "address: " + (b1 & 0x7F)
230                + ", data bytes: 0x" + StringUtil.twoHexFromInt(b2)
231                + " 0x" + StringUtil.twoHexFromInt(b4)
232                + " => ";
233
234        if ((b2 == 122) && ((b4 & 0x70) == 0x10)) {
235            result += "Shift to high 24 outputs";
236            return result;
237        } else if ((b2 == b4) && (b2 == 0x77)) {
238            result += "software version query";
239            return result;
240        } else if ((b2 == 0x70) && ((b4 & 0xF0) == 0x10)) {
241            result += "Initialize parallel sensors";
242            return result;
243        } else if ((b2 == 0x71) && ((b4 & 0xF0) == 0x00)) {
244            result += "Initialize ASD sensors";
245            return result;
246        } else // check various bank forms 
247        if ((b4 & 0xF0) <= 0x30) {
248            // Bank 0-3 - signal command
249            result += "bank " + ((b4 & 0xF0) >> 4) + " signal " + ((b2 & 0x78) >> 3);
250            int cmd = b2 & 0x07;
251            result += " cmd " + cmd;
252            result += " (set " + colorAsString(cmd);
253            if (cmd == 0) {
254                result += "/closed";
255            }
256            if (cmd == 6) {
257                result += "/thrown";
258            }
259            result += ")";
260            return result;
261        } else if ((b4 & 0xF0) == 0x40) {
262            // bank 4 - new serial sensor message
263            result += "serial sensor bit " + (((b2 & 0x7E) >> 1) + 1) + " is " + (((b2 & 0x01) == 0) ? "active" : "inactive");
264            return result;
265        } else if ((b4 & 0xF0) == 0x50) {
266            // bank 5 - sensor message
267            if ((b2 & 0x20) == 0) {
268                // parallel sensor
269                if ((b2 & 0x40) != 0) {
270                    result += "2nd connector ";
271                }
272                result += "parallel sensor " + ((b2 & 0x10) != 0 ? "high" : "low") + " nibble:";
273            } else {
274                // old serial sensor
275                result += "older serial sensor " + ((b2 & 0x10) != 0 ? "high" : "low") + " nibble:";
276            }
277            // add bits
278            result += ((b2 & 0x08) == 0) ? " A" : " I";
279            result += ((b2 & 0x04) == 0) ? " A" : " I";
280            result += ((b2 & 0x02) == 0) ? " A" : " I";
281            result += ((b2 & 0x01) == 0) ? " A" : " I";
282            return result;
283        } else {
284            // other banks
285            return result + "bank " + ((b4 & 0xF0) >> 4) + ", unknown message";
286        }
287    }
288
289    static String[] colors = new String[]{"green", "flashing green", "yellow", "flashing yellow", "off", "flashing off", "red", "flashing red"};
290
291    static String colorAsString(int color) {
292        return colors[color];
293    }
294
295    private final static Logger log = LoggerFactory.getLogger(SerialMessage.class);
296
297}