001package jmri.jmrix.qsi; 002 003import jmri.ProgrammingMode; 004import jmri.util.StringUtil; 005 006/** 007 * Encodes a message to an QSI command station. 008 * <p> 009 * The {@link QsiReply} class handles the response from the command station. 010 * 011 * @author Bob Jacobsen Copyright (C) 2007, 2008 012 */ 013public class QsiMessage extends jmri.jmrix.AbstractMessage { 014 015 // Special characters (NOTE: microchip bootloader does not use standard ASCII) 016 public static final int STX = 15; 017 public static final int DLE = 5; 018 public static final int ETX = 4; 019 public static final int CR = 0x0d; 020 public static final int LF = 0x0a; 021 022 // bootloader commands 023 public static final int RD_VER = 0; 024 public static final int WT_FLASH = 2; 025 public static final int ER_FLASH = 3; 026 public static final int WT_EEDATA = 5; 027 028 // Longest boot message is 256bytes each preceded by DLE + 2xSTX + ETX 029 static final int MAXSIZE = 515; 030 031 // create a new one 032 public QsiMessage(int i) { 033 super(i); 034 } 035 036 // from String 037 public QsiMessage(String s) { 038 super(s); 039 } 040 041 // copy one 042 public QsiMessage(QsiMessage m) { 043 super(m); 044 } 045 046 public void setOpCode(int i) { 047 _dataChars[0] = i; 048 } 049 050 public int getOpCode() { 051 return _dataChars[0]; 052 } 053 054 public String getOpCodeHex() { 055 return "0x" + Integer.toHexString(getOpCode()); 056 } 057 058 public void setLength(int i) { 059 _dataChars[1] = i; 060 } 061 062 public void setV4Length(int i) { 063 _dataChars[0] = hexDigit((i & 0xf0) >> 4); 064 _dataChars[1] = hexDigit(i & 0xf); 065 } 066 067 public void setAddress(int i) { 068 _dataChars[2] = i & 0xff; 069 _dataChars[3] = (i >> 8) & 0xff; 070 _dataChars[4] = i >> 16; 071 } 072 073 public void setV4Address(int i) { 074 _dataChars[2] = hexDigit((i & 0xf000) >> 12); 075 _dataChars[3] = hexDigit((i & 0xf00) >> 8); 076 _dataChars[4] = hexDigit((i & 0xf0) >> 4); 077 _dataChars[5] = hexDigit(i & 0xf); 078 } 079 080 public void setV4RecType(int i) { 081 _dataChars[6] = hexDigit((i & 0xf0) >> 4); 082 _dataChars[7] = hexDigit(i & 0xf); 083 } 084 085 public void setData(int[] d) { 086 System.arraycopy(d, 0, _dataChars, 5, d.length); 087 } 088 089 public void setV4Data(int[] d) { 090 int j = 8; 091 for (int i = 0; i < d.length; i++) { 092 _dataChars[j++] = hexDigit((d[i] & 0xf0) >> 4); 093 _dataChars[j++] = hexDigit(d[i] & 0xf); 094 } 095 } 096 097 public void setChecksum() { 098 int checksum = 0; 099 for (int i = 0; i < _nDataChars - 1; i++) { 100 checksum += _dataChars[i]; 101 } 102 checksum = checksum & 0xff; 103 if (checksum > 0) { 104 checksum = 256 - checksum; 105 } 106 _dataChars[_nDataChars - 1] = checksum; 107 } 108 109 public void setV4Checksum(int length, int addr, int type, int[] data) { 110 int checksum = length + ((addr & 0xff00) >> 8) + (addr & 0xff) + type; 111 for (int i = 0; i < data.length; i++) { 112 checksum += data[i]; 113 } 114 checksum = checksum & 0xff; 115 if (checksum > 0) { 116 checksum = 256 - checksum; 117 } 118 _dataChars[_nDataChars - 2] = hexDigit((checksum & 0xf0) >> 4); 119 _dataChars[_nDataChars - 1] = hexDigit(checksum & 0x0f); 120 } 121 122 private int hexDigit(int b) { 123 if (b > 9) { 124 return (b - 9 + 0x40); 125 } else { 126 return (b + 0x30); 127 } 128 } 129 130 public QsiMessage frame() { 131 int j = 2; 132 // Create new message to hold the framed one 133 QsiMessage f = new QsiMessage(MAXSIZE); 134 f.setElement(0, STX); 135 f.setElement(1, STX); 136 // copy existing message adding DLE 137 for (int i = 0; i < _nDataChars; i++) { 138 if (_dataChars[i] == STX 139 || _dataChars[i] == ETX 140 || _dataChars[i] == DLE) { 141 f.setElement(j++, DLE); 142 } 143 f.setElement(j++, _dataChars[i]); 144 } 145 f.setElement(j++, ETX); 146 f._nDataChars = j; 147 // return new message 148 return f; 149 } 150 151 public QsiMessage v4frame() { 152 int i; 153 // Create new message to hold the framed one 154 QsiMessage f = new QsiMessage(MAXSIZE); 155 f.setElement(0, ':'); 156 // copy existing message adding CRLF 157 for (i = 1; i <= _nDataChars; i++) { 158 f.setElement(i, _dataChars[i - 1]); 159 } 160 f.setElement(i++, CR); 161 f.setElement(i++, LF); 162 f._nDataChars = i; 163 // return new message 164 return f; 165 } 166 167 @Override 168 public String toString() { 169 QsiSystemConnectionMemo memo = jmri.InstanceManager.getDefault(jmri.jmrix.qsi.QsiSystemConnectionMemo.class); 170 return toString(memo.getQsiTrafficController()); 171 } 172 173 public String toString(QsiTrafficController controller) { 174 if (_dataChars == null) { 175 return "<none>"; 176 } 177 StringBuilder s = new StringBuilder(""); 178 if (controller == null || controller.isSIIBootMode()) { 179 for (int i = 0; i < _nDataChars; i++) { 180 s.append(StringUtil.twoHexFromInt(_dataChars[i])).append(" "); 181 } 182 } else { 183 for (int i = 0; i < _nDataChars; i++) { 184 s.append("<").append(_dataChars[i]).append(">"); 185 } 186 } 187 return s.toString(); 188 } 189 190 // diagnose format 191 public boolean isKillMain() { 192 return getOpCode() == '-'; 193 } 194 195 public boolean isEnableMain() { 196 return getOpCode() == '+'; 197 } 198 199 // static methods to return a formatted message 200 static public QsiMessage getEnableMain() { 201 QsiMessage m = new QsiMessage(1); 202 m.setOpCode('+'); 203 return m; 204 } 205 206 static public QsiMessage getKillMain() { 207 QsiMessage m = new QsiMessage(1); 208 m.setOpCode('-'); 209 return m; 210 } 211 212 static public QsiMessage getProgMode() { 213 QsiMessage m = new QsiMessage(1); 214 m.setOpCode('P'); 215 return m; 216 } 217 218 // [AC] 11/09/2002 Leave QSI in programmer mode. Don't want to go 219 // to booster mode as this would power up the track. 220 static public QsiMessage getExitProgMode() { 221 QsiMessage m = new QsiMessage(1); 222 m.setOpCode(' '); 223 return m; 224 } 225 226 static public QsiMessage getClearStatus() { 227 // OP_REQ_CLEAR_ERROR_STATUS 228 QsiMessage m = new QsiMessage(3); 229 m.setElement(0, 17); 230 m.setElement(1, 0); 231 m.setElement(2, 0); 232 return m; 233 } 234 235 static public QsiMessage getReadCV(int cv, ProgrammingMode mode) { 236 // OP_REQ_READ_CV 237 QsiMessage m = new QsiMessage(4); 238 m.setElement(0, 9); 239 m.setElement(1, 1); 240 m.setElement(2, 0); 241 m.setElement(3, cv); 242 return m; 243 } 244 245 static public QsiMessage getWriteCV(int cv, int val, ProgrammingMode mode) { 246 // OP_REQ_WRITE_CV 247 QsiMessage m = new QsiMessage(5); 248 m.setElement(0, 30); 249 m.setElement(1, 2); 250 m.setElement(2, 0); 251 m.setElement(3, cv); 252 m.setElement(4, val); 253 return m; 254 } 255 256 // [AC] 11/09/2002 QSI doesn't currently support registered mode 257 static public QsiMessage getReadRegister(int reg) { //Vx 258 // if (reg>8) log.error("register number too large: "+reg); 259 // QsiMessage m = new QsiMessage(2); 260 // m.setOpCode('V'); 261 // String s = ""+reg; 262 // m.setElement(1, s.charAt(s.length()-1)); 263 // return m; 264 QsiMessage m = new QsiMessage(1); 265 m.setOpCode(' '); 266 return m; 267 } 268 269 static public QsiMessage getWriteRegister(int reg, int val) { //Sx xx 270 // if (reg>8) log.error("register number too large: "+reg); 271 // QsiMessage m = new QsiMessage(4); 272 // m.setOpCode('S'); 273 // String s = ""+reg; 274 // m.setElement(1, s.charAt(s.length()-1)); 275 // addIntAsTwoHex(val, m, 2); 276 // return m; 277 QsiMessage m = new QsiMessage(1); 278 m.setOpCode(' '); 279 return m; 280 } 281 282 // Bootloader messages are initially created long enough for 283 // the message and checksum. The message is then framed with control 284 // characters before being returned 285 static public QsiMessage getReadBootVersion() { 286 QsiMessage m = new QsiMessage(3); 287 m.setOpCode(RD_VER); 288 m.setLength(2); 289 m.setChecksum(); 290 return m.frame(); 291 } 292 293 static public QsiMessage getWriteFlash(int addr, int[] data) { 294 int l = data.length; 295 // Writes are rounded up to multiples of 8 bytes 296 if (l % 8 != 0) { 297 l = l + (8 - l % 8); 298 } 299 // and data padded with erased condition 300 int padded[] = new int[l]; 301 for (int i = 0; i < l; i++) { 302 if (i < data.length) { 303 padded[i] = data[i]; 304 } else { 305 padded[i] = 0xff; 306 } 307 } 308 QsiMessage m = new QsiMessage(6 + l); 309 m.setOpCode(WT_FLASH); 310 // length is number of 8 byte blocks 311 m.setLength(l / 8); 312 m.setAddress(addr); 313 m.setData(padded); 314 m.setChecksum(); 315 return m.frame(); 316 } 317 318 static public QsiMessage getV4WriteFlash(int addr, int[] data, int type) { 319 // Create a v4 bootloader message which is same format as a record 320 // in the hex file 321 int l = (data.length + 5) * 2; 322 QsiMessage m = new QsiMessage(l); 323 m.setV4Length(data.length); 324 m.setV4Address(addr); 325 m.setV4RecType(type); 326 m.setV4Data(data); 327 m.setV4Checksum(data.length, addr, type, data); 328 return m.v4frame(); 329 } 330 331 static public QsiMessage getV4EndOfFile() { 332 // Create a v4 bootloader end of file message 333 int l = 10; 334 QsiMessage m = new QsiMessage(l); 335 m.setV4Length(0); 336 m.setV4Address(0); 337 m.setV4RecType(1); 338 m.setV4Checksum(0, 0, 1, new int[0]); 339 return m.v4frame(); 340 } 341 342 static public QsiMessage getv4ExtAddr() { 343 // Create a v4 bootloader extended address message 344 int l = 14; 345 int[] data = {0, 0}; 346 QsiMessage m = new QsiMessage(l); 347 m.setV4Length(2); 348 m.setV4Address(0); 349 m.setV4RecType(4); 350 m.setV4Data(data); 351 m.setV4Checksum(0, 0, 4, data); 352 return m.v4frame(); 353 } 354 355 static public QsiMessage getEraseFlash(int addr, int rows) { 356 QsiMessage m = new QsiMessage(6); 357 m.setOpCode(ER_FLASH); 358 // Erase a number of 64 byte rows 359 m.setLength(rows); 360 m.setAddress(addr); 361 m.setChecksum(); 362 return m.frame(); 363 } 364 365 static public QsiMessage getWriteEE(int addr, int[] data) { 366 QsiMessage m = new QsiMessage(6 + data.length); 367 m.setOpCode(WT_EEDATA); 368 m.setLength(data.length); 369 m.setAddress(addr & 0xff); 370 m.setData(data); 371 m.setChecksum(); 372 return m.frame(); 373 } 374 375 static public QsiMessage getReset() { 376 QsiMessage m = new QsiMessage(3); 377 m.setOpCode(0); 378 m.setLength(0); 379 m.setChecksum(); 380 return m.frame(); 381 } 382 383}