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}