001package jmri.jmrix.can.adapters.gridconnect; 002 003import jmri.jmrix.AbstractMRReply; 004import jmri.jmrix.can.CanReply; 005import org.slf4j.Logger; 006import org.slf4j.LoggerFactory; 007 008/** 009 * Class for replies in a GridConnect based message/reply protocol. 010 * <p> 011 * The GridConnect protocol encodes messages as an ASCII string of up to 24 012 * characters of the form: :ShhhhNd0d1d2d3d4d5d6d7; 013 * <p> 014 * hhhh is the two byte (11 015 * useful bits) header The S indicates a standard CAN frame 016 * :XhhhhhhhhNd0d1d2d3d4d5d6d7; The X indicates an extended CAN frame N or R 017 * indicates a normal or remote frame, in position 6 or 10 d0 - d7 are the (up 018 * to) 8 data bytes 019 * 020 * @author Andrew Crosland Copyright (C) 2008, 2009 021 * @author Bob Jacobsen Copyright (C) 2008 022 */ 023public class GridConnectReply extends AbstractMRReply { 024 025 static final int MAXLEN = 27; 026 027 /** 028 * Creates a new instance of GridConnectReply. 029 */ 030 public GridConnectReply() { 031 _nDataChars = 0; 032 _dataChars = new int[MAXLEN]; 033 } 034 035 /** 036 * Creates a new GridConnectReply from String. 037 * @param s String to use as basis for the GCReply. 038 */ 039 public GridConnectReply(String s) { 040 _nDataChars = s.length(); 041 for (int i = 0; i < s.length(); i++) { 042 _dataChars[i] = s.charAt(i); 043 } 044 } 045 046 /** 047 * Create a CanReply from a GridConnectReply. 048 * @return new CanReply Outgoing message. 049 */ 050 public CanReply createReply() { 051 CanReply ret = new CanReply(); 052 053 log.debug("createReply converts from {}", this); 054 055 // basic checks drop out the frame 056 if (!basicFormatCheck()) { 057 ret.setHeader(0); 058 ret.setNumDataElements(0); 059 return ret; 060 } 061 062 // Is it an Extended frame? 063 if (isExtended()) { 064 ret.setExtended(true); 065 } 066 067 // Copy the header 068 ret.setHeader(getHeader()); 069 070 // Is it an RTR frame? 071 if (isRtr()) { 072 ret.setRtr(true); 073 } 074 075 // Get the data 076 for (int i = 0; i < getNumBytes(); i++) { 077 ret.setElement(i, getByte(i)); 078 } 079 ret.setNumDataElements(getNumBytes()); 080 log.debug("createReply converted to {}", ret); 081 return ret; 082 } 083 084 /** 085 * Check if this GCReply contains an Extended or Standard flag. 086 * @return true if contains a flag, else false. 087 */ 088 protected boolean basicFormatCheck() { 089 return !((getElement(1) != 'X') && (getElement(1) != 'S')); 090 } 091 092 /** 093 * {@inheritDoc} 094 */ 095 @Override 096 protected int skipPrefix(int index) { 097 while (_dataChars[index] == ':') { 098 index++; 099 } 100 return index; 101 } 102 103 /** 104 * {@inheritDoc} 105 */ 106 @Override 107 public int getNumDataElements() { 108 return _nDataChars; 109 } 110 111 /** 112 * Set Number of Data Elements. 113 * Max. length set by the MAXLEN constant. 114 * @param n Number Elements. 115 */ 116 public void setNumDataElements(int n) { 117 _nDataChars = (n <= MAXLEN) ? n : MAXLEN; 118 } 119 120 /** 121 * {@inheritDoc} 122 */ 123 @Override 124 public int getElement(int n) { 125 return _dataChars[n]; 126 } 127 128 /** 129 * {@inheritDoc} 130 */ 131 @Override 132 public void setElement(int n, int v) { 133 if (n < MAXLEN) { 134 _dataChars[n] = v; 135 _nDataChars = Math.max(_nDataChars, n + 1); 136 } 137 } 138 139 /** 140 * Get if the GridConnectReply is Extended. 141 * @return true if extended, else false. 142 */ 143 public boolean isExtended() { 144 return (getElement(1) == 'X'); 145 } 146 147 /** 148 * Get if the GridConnectReply is RtR. 149 * @return true if RtR, else false. 150 */ 151 public boolean isRtr() { 152 return (getElement(_RTRoffset) == 'R'); 153 } 154 155 /** 156 * {@inheritDoc} 157 */ 158 @Override 159 public int maxSize() { 160 return MAXLEN; 161 } 162 163 /** 164 * Set the GridConnectReply data by Array. 165 * @param d data array. 166 */ 167 public void setData(int[] d) { 168 int len = (d.length <= MAXLEN) ? d.length : MAXLEN; 169 System.arraycopy(d, 0, _dataChars, 0, len); 170 } 171 172 // pointer to the N or R character 173 int _RTRoffset = -1; 174 175 /** 176 * Get the CAN header by using chars from 2 to up to 9. 177 * <p> 178 * Right justify standard headers that had 4 digits. 179 * 180 * @return the CAN header as an int 181 */ 182 public int getHeader() { 183 int val = 0; 184 for (int i = 2; i <= 10; i++) { 185 _RTRoffset = i; 186 if (_dataChars[i] == 'N') { 187 break; 188 } 189 if (_dataChars[i] == 'R') { 190 break; 191 } 192 val = val * 16 + getHexDigit(i); 193 } 194 return val; 195 } 196 197 /** 198 * Get the number of data bytes. 199 * @return number of bytes in reply. 200 */ 201 public int getNumBytes() { 202 // subtract framing and ID bytes, etc and each byte is two ASCII hex digits 203 return (_nDataChars - (_RTRoffset + 1)) / 2; 204 } 205 206 /** 207 * Get a hex data byte from the message. 208 * <p> 209 * Data bytes are encoded as two ASCII hex digits starting at byte 7 of the 210 * message. 211 * 212 * @param b The byte offset (0 - 7) 213 * @return The value 214 */ 215 public int getByte(int b) { 216 if ((b >= 0) && (b <= 7)) { 217 int index = b * 2 + _RTRoffset + 1; 218 int hi = getHexDigit(index++); 219 int lo = getHexDigit(index); 220 if ((hi < 16) && (lo < 16)) { 221 return (hi * 16 + lo); 222 } 223 } 224 return 0; 225 } 226 227 // Get a single hex digit. returns 0 if digit is invalid 228 private int getHexDigit(int index) { 229 int b = _dataChars[index]; 230 if ((b >= '0') && (b <= '9')) { 231 b = b - '0'; 232 } else if ((b >= 'A') && (b <= 'F')) { 233 b = b - 'A' + 10; 234 } else if ((b >= 'a') && (b <= 'f')) { 235 b = b - 'a' + 10; 236 } else { 237 b = 0; 238 } 239 return (byte) b; 240 } 241 242 private final static Logger log = LoggerFactory.getLogger(GridConnectReply.class); 243} 244 245