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}