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