001package jmri.jmrix.roco.z21; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import jmri.jmrix.AbstractMRReply; 005import jmri.DccLocoAddress; 006 007/** 008 * Class for replies in the z21/Z21 protocol. 009 * <p> 010 * Replies are of the format: 2 bytes length 2 bytes opcode n bytes data 011 * <p> 012 * numeric data is sent in little endian format. 013 * 014 * @author Bob Jacobsen Copyright (C) 2003 015 * @author Paul Bender Copyright (C) 2014 016 */ 017public class Z21Reply extends AbstractMRReply { 018 019 private static final String WRONG_REPLY_TYPE = "Wrong Reply Type"; 020 021 /** 022 * Create a new one. 023 */ 024 public Z21Reply() { 025 super(); 026 setBinary(true); 027 } 028 029 /** 030 * This ctor interprets the byte array as a sequence of characters to send. 031 * 032 * @param a Array of bytes to send. 033 * @param l length of reply. 034 */ 035 public Z21Reply(byte[] a, int l) { 036 super(); 037 _nDataChars = l; 038 setBinary(true); 039 for (int i = 0; i < _nDataChars; i++) { 040 _dataChars[i] = a[i]; 041 } 042 } 043 044 // keep track of length 045 @Override 046 public void setElement(int n, int v) { 047 _dataChars[n] = (char) v; 048 _nDataChars = Math.max(_nDataChars, n + 1); 049 } 050 051 /** 052 * Get an integer representation of a BCD value. 053 * 054 * @param n byte in message to convert 055 * @return Integer value of BCD byte. 056 */ 057 public Integer getElementBCD(int n) { 058 return Integer.decode(Integer.toHexString(getElement(n))); 059 } 060 061 @Override 062 public void setOpCode(int i) { 063 _dataChars[2] = (char) (i & 0x00ff); 064 _dataChars[3] = (char) ((i & 0xff00) >> 8); 065 _nDataChars = Math.max(_nDataChars, 4); //smallest reply is of length 4. 066 } 067 068 @Override 069 public int getOpCode() { 070 return (0xff&_dataChars[2]) + ((0xff&_dataChars[3]) << 8); 071 } 072 073 public void setLength(int i) { 074 _dataChars[0] = (char) (i & 0x00ff); 075 _dataChars[1] = (char) ((i & 0xff00) >> 8); 076 _nDataChars = Math.max(_nDataChars, i); 077 } 078 079 public int getLength() { 080 return (0xff & _dataChars[0] ) + ((0xff & _dataChars[1]) << 8); 081 } 082 083 @Override 084 protected int skipPrefix(int index) { 085 return 0; 086 } 087 088 @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH") 089 @Override 090 public String toMonitorString() { 091 switch(getOpCode()){ 092 case 0x0010: 093 int serialNo = (getElement(4)&0xff) + ((getElement(5)&0xff) << 8) 094 + ((getElement(6)&0xff) << 16) + ((getElement(7)&0xff) << 24); 095 return Bundle.getMessage("Z21ReplyStringSerialNo", serialNo); 096 case 0x001A: 097 int hwversion = getElement(4) + (getElement(5) << 8) + 098 (getElement(6) << 16 ) + (getElement(7) << 24 ); 099 float swversion = (getElementBCD(8)/100.0f)+ 100 (getElementBCD(9))+ 101 (getElementBCD(10)*100)+ 102 (getElementBCD(11))*10000; 103 return Bundle.getMessage("Z21ReplyStringVersion",java.lang.Integer.toHexString(hwversion), swversion); 104 case 0x0040: 105 return Bundle.getMessage("Z21XpressNetTunnelReply", getXNetReply().toMonitorString()); 106 case 0x0051: 107 return Bundle.getMessage("Z21ReplyBroadcastFlags",Z21MessageUtils.interpretBroadcastFlags(_dataChars)); 108 case 0x0080: 109 int groupIndex = getElement(4) & 0xff; 110 int offset = (groupIndex * 10) + 1; 111 String[] moduleStatus = new String[10]; 112 for(int i=0;i<10;i++){ 113 moduleStatus[i]= Bundle.getMessage("RMModuleFeedbackStatus",offset + i, 114 Bundle.getMessage("RMModuleContactStatus",1, ((getElement(i+5)&0x01)==0x01)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")), 115 Bundle.getMessage("RMModuleContactStatus",2, ((getElement(i+5)&0x02)==0x02)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")), 116 Bundle.getMessage("RMModuleContactStatus",3, ((getElement(i+5)&0x04)==0x04)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")), 117 Bundle.getMessage("RMModuleContactStatus",4, ((getElement(i+5)&0x08)==0x08)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")), 118 Bundle.getMessage("RMModuleContactStatus",5, ((getElement(i+5)&0x10)==0x10)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")), 119 Bundle.getMessage("RMModuleContactStatus",6, ((getElement(i+5)&0x20)==0x20)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")), 120 Bundle.getMessage("RMModuleContactStatus",7, ((getElement(i+5)&0x40)==0x40)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff")), 121 Bundle.getMessage("RMModuleContactStatus",8, ((getElement(i+5)&0x80)==0x80)? Bundle.getMessage("PowerStateOn") : Bundle.getMessage("PowerStateOff"))); 122 } 123 return Bundle.getMessage("RMBusFeedbackStatus",groupIndex, 124 moduleStatus[0],moduleStatus[1],moduleStatus[2], 125 moduleStatus[3],moduleStatus[4],moduleStatus[5], 126 moduleStatus[6],moduleStatus[7],moduleStatus[8], 127 moduleStatus[9]); 128 case 0x0084: 129 int mainCurrent = getSystemDataMainCurrent(); 130 int progCurrent = getSystemDataProgCurrent(); 131 int filteredMainCurrent = getSystemDataFilteredMainCurrent(); 132 int temperature = getSystemDataTemperature(); 133 int supplyVolts = getSystemDataSupplyVoltage(); 134 int internalVolts = getSystemDataVCCVoltage(); 135 int state = getElement(16); 136 int extendedState = getElement(17); 137 // data bytes 14 and 15 (offset 18 and 19) are reserved. 138 return Bundle.getMessage("Z21SystemStateReply",mainCurrent, 139 progCurrent,filteredMainCurrent,temperature, 140 supplyVolts,internalVolts,state,extendedState); 141 case 0x00A0: 142 return Bundle.getMessage("Z21LocoNetRxReply", getLocoNetMessage().toMonitorString()); 143 case 0x00A1: 144 return Bundle.getMessage("Z21LocoNetTxReply", getLocoNetMessage().toMonitorString()); 145 case 0x00A2: 146 return Bundle.getMessage("Z21LocoNetLanReply", getLocoNetMessage().toMonitorString()); 147 case 0x0088: 148 int entries = getNumRailComDataEntries(); 149 StringBuilder datastring = new StringBuilder(); 150 for(int i = 0; i < entries ; i++) { 151 DccLocoAddress address = getRailComLocoAddress(i); 152 int rcvCount = getRailComRcvCount(i); 153 int errorCount = getRailComErrCount(i); 154 int speed = getRailComSpeed(i); 155 int options = getRailComOptions(i); 156 int qos = getRailComQos(i); 157 datastring.append(Bundle.getMessage("Z21_RAILCOM_DATA",address,rcvCount,errorCount,options,speed,qos)); 158 datastring.append("\n"); 159 } 160 return Bundle.getMessage("Z21_RAILCOM_DATACHANGED",entries,new String(datastring)); 161 case 0x00C4: 162 int networkID = ( getElement(4)&0xFF) + ((getElement(5)&0xFF) << 8); 163 int address = ( getElement(6)&0xFF) + ((getElement(7)&0xFF) << 8); 164 int port = ( getElement(8) & 0xFF); 165 int type = ( getElement(9) & 0xFF); 166 int value1 = (getElement(10)&0xFF) + ((getElement(11)&0xFF) << 8); 167 int value2 = (getElement(12)&0xFF) + ((getElement(13)&0xFF) << 8); 168 String typeString = ""; 169 String value1String; 170 String value2String = ""; 171 switch(type){ 172 case 0x01: 173 typeString = "Input Status"; 174 switch(value1){ 175 case 0x0000: 176 value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_FREE_WITHOUT"); 177 break; 178 case 0x0100: 179 value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_FREE_WITH"); 180 break; 181 case 0x1000: 182 value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_BUSY_WITHOUT"); 183 break; 184 case 0x1100: 185 value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_BUSY_WITH"); 186 break; 187 case 0x1201: 188 value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_OVERLOAD_1"); 189 break; 190 case 0x1202: 191 value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_OVERLOAD_2"); 192 break; 193 case 0x1203: 194 value1String = Bundle.getMessage("Z21_CAN_INPUT_STATUS_OVERLOAD_3"); 195 break; 196 default: 197 value1String = "<unknown>"; 198 } 199 break; 200 case 0x11: 201 case 0x12: 202 case 0x13: 203 case 0x14: 204 case 0x15: 205 case 0x16: 206 case 0x17: 207 case 0x18: 208 case 0x19: 209 case 0x1A: 210 case 0x1B: 211 case 0x1C: 212 case 0x1D: 213 case 0x1E: 214 case 0x1F: 215 typeString = "Occupancy Info"; 216 value1String = getCanDetectorLocoAddressString(value1); 217 value2String = getCanDetectorLocoAddressString(value2); 218 break; 219 default: 220 value1String = "" + value1; 221 value2String = "" + value2; 222 } 223 224 return Bundle.getMessage("Z21CANDetectorReply",Integer.toHexString(networkID),address,port,typeString,value1String,value2String); 225 226 default: 227 } 228 229 return toString(); 230 } 231 232 // handle XpressNet replies tunneled in Z21 messages 233 boolean isXPressNetTunnelMessage() { 234 return (getOpCode() == 0x0040); 235 } 236 237 Z21XNetReply getXNetReply() { 238 Z21XNetReply xnr = null; 239 if (isXPressNetTunnelMessage()) { 240 int i = 4; 241 xnr = new Z21XNetReply(); 242 for (; i < getLength(); i++) { 243 xnr.setElement(i - 4, getElement(i)); 244 } 245 if(( xnr.getElement(0) & 0x0F ) > ( xnr.getNumDataElements()+2) ){ 246 // there is at least one message from the Z21 that can be sent 247 // with fewer bytes than the XpressNet payload indicates it 248 // should have. Pad those messages with 0x00 bytes. 249 for(i=i-4;i<((xnr.getElement(0)&0x0F)+2);i++){ 250 xnr.setElement(i,0x00); 251 } 252 } 253 } 254 return xnr; 255 } 256 257 // handle RailCom data replies 258 boolean isRailComDataChangedMessage(){ 259 return (getOpCode() == 0x0088); 260 } 261 262 /** 263 * @return the number of RailCom entries in this message. 264 * the returned value is in the 0 to 19 range. 265 */ 266 int getNumRailComDataEntries(){ 267 if(!this.isRailComDataChangedMessage()){ 268 return 0; // this isn't a RailCom message, so there are no entries. 269 } 270 // if this is a RailCom message, the length field is 271 // then the entries are n=(len-4)/13, per the Z21 protocol 272 // manual, section 8.1. Also, 0<=n<=19 273 return ((getLength() - 4)/13); 274 } 275 276 /** 277 * Get a locomotive address from an entry in a railcom message. 278 * 279 * @param n the entry to get the address from. 280 * @return the locomotive address for the specified entry. 281 */ 282 DccLocoAddress getRailComLocoAddress(int n){ 283 int offset = 4+(n*13); // +4 to get past header 284 int address = Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 285 return new DccLocoAddress(address,address>=100); 286 } 287 288 /** 289 * Get the receive counter from an entry in a railcom message. 290 * 291 * @param n the entry to get the address from. 292 * @return the receive counter for the specified entry. 293 */ 294 int getRailComRcvCount(int n){ 295 int offset = 6+(n*13); // +6 to get header and address. 296 return ((0xff&getElement(offset+3))<<24) + 297 ((0xff&(getElement(offset+2))<<16) + 298 ((0xff&getElement(offset+1))<<8) + 299 (0xff&(getElement(offset)))); 300 } 301 302 /** 303 * Get the error counter from an entry in a railcom message. 304 * 305 * @param n the entry to get the address from. 306 * @return the error counter for the specified entry. 307 */ 308 int getRailComErrCount(int n){ 309 int offset = 10+(n*13); // +10 to get past header, address,and rcv count. 310 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 311 } 312 313 /** 314 * Get the speed value from an entry in a railcom message. 315 * 316 * @param n the entry to get the address from. 317 * @return the error counter for the specified entry. 318 */ 319 int getRailComSpeed(int n){ 320 int options = getRailComOptions(n); 321 if(((options & 0x01) == 0x01) || ((options & 0x02) == 0x02)) { 322 int offset = 14+(n*13); //+14 to get past the options, 323 // and everything before the options. 324 return (0xff&(getElement(offset))); 325 } else { 326 return 0; 327 } 328 } 329 330 /** 331 * Get the options value from an entry in a railcom message. 332 * 333 * @param n the entry to get the address from. 334 * @return the options for the specified entry. 335 */ 336 int getRailComOptions(int n){ 337 int offset = 13+(n*13); //+13 to get past the header, address, rcv 338 // counter, and reserved byte. 339 return (0xff&(getElement(offset))); 340 } 341 342 /** 343 * Get the Quality of Service value from an entry in a railcom message. 344 * 345 * @param n the entry to get the address from. 346 * @return the Quality of Service value for the specified entry. 347 */ 348 int getRailComQos(int n){ 349 if((getRailComOptions(n) & 0x04) == 0x04 ) { 350 int offset = 15+(n*13); //+15 to get past the speed, 351 // and everything before the speed. 352 return (0xff&(getElement(offset))); 353 } else { 354 return 0; // if the QOS bit isn't set, there is no QOS attribute. 355 } 356 } 357 358 // handle System data replies 359 boolean isSystemDataChangedReply(){ 360 return (getOpCode() == 0x0084); 361 } 362 363 private void checkSystemDataChangeReply(){ 364 if(!isSystemDataChangedReply()){ 365 throw new IllegalArgumentException(WRONG_REPLY_TYPE); 366 } 367 } 368 369 /** 370 * Get the Main Track Current from the SystemStateDataChanged 371 * message. 372 * 373 * @return the current in mA. 374 */ 375 int getSystemDataMainCurrent(){ 376 checkSystemDataChangeReply(); 377 int offset = 4; //skip the headers 378 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 379 } 380 381 /** 382 * Get the Programming Track Current from the SystemStateDataChanged 383 * message. 384 * 385 * @return the current in mA. 386 */ 387 int getSystemDataProgCurrent(){ 388 checkSystemDataChangeReply(); 389 int offset = 6; //skip the headers 390 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 391 } 392 393 /** 394 * Get the Filtered Main Track Current from the SystemStateDataChanged 395 * message. 396 * 397 * @return the current in mA. 398 */ 399 int getSystemDataFilteredMainCurrent(){ 400 checkSystemDataChangeReply(); 401 int offset = 8; //skip the headers 402 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 403 } 404 405 /** 406 * Get the Temperature from the SystemStateDataChanged 407 * message. 408 * 409 * @return the current in degrees C. 410 */ 411 int getSystemDataTemperature(){ 412 checkSystemDataChangeReply(); 413 int offset = 10; //skip the headers 414 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 415 } 416 417 /** 418 * Get the Supply Voltage from the SystemStateDataChanged 419 * message. 420 * 421 * @return the current in mV. 422 */ 423 int getSystemDataSupplyVoltage(){ 424 checkSystemDataChangeReply(); 425 int offset = 12; //skip the headers 426 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 427 } 428 429 /** 430 * Get the VCC (and track) Voltage from the SystemStateDataChanged 431 * message. 432 * 433 * @return the current in mV. 434 */ 435 int getSystemDataVCCVoltage(){ 436 checkSystemDataChangeReply(); 437 int offset = 14; //skip the headers 438 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 439 } 440 441 // handle LocoNet replies tunneled in Z21 messages 442 boolean isLocoNetTunnelMessage() { 443 switch (getOpCode()){ 444 case 0xA0: // LAN_LOCONET_Z21_RX 445 case 0xA1: // LAN_LOCONET_Z21_TX 446 case 0xA2: // LAN_LOCONET_FROM_LAN 447 return true; 448 default: 449 return false; 450 } 451 } 452 453 boolean isLocoNetDispatchMessage() { 454 return (getOpCode() == 0xA3); 455 } 456 457 boolean isLocoNetDetectorMessage() { 458 return (getOpCode() == 0xA4); 459 } 460 461 jmri.jmrix.loconet.LocoNetMessage getLocoNetMessage() { 462 jmri.jmrix.loconet.LocoNetMessage lnr = null; 463 if (isLocoNetTunnelMessage()) { 464 int i = 4; 465 lnr = new jmri.jmrix.loconet.LocoNetMessage(getLength()-4); 466 for (; i < getLength(); i++) { 467 lnr.setElement(i - 4, getElement(i)); 468 } 469 } 470 return lnr; 471 } 472 473 // handle RMBus data replies 474 boolean isRMBusDataChangedReply(){ 475 return (getOpCode() == 0x0080); 476 } 477 478 // handle CAN Feedback/Railcom replies 479 boolean isCanDetectorMessage() { 480 return (getOpCode() == 0x00C4); 481 } 482 483 // address value is the 16 bits of the two bytes containing the 484 // address. The most significan two bits represent the direction. 485 String getCanDetectorLocoAddressString(int addressValue) { 486 if(!isCanDetectorMessage()) { 487 return ""; 488 } 489 String addressString; 490 if(addressValue==0) { 491 addressString="end of list"; 492 } else { 493 addressString = "" + (getCanDetectorLocoAddress(addressValue)).toString(); 494 int direction = (0xC000&addressValue); 495 switch (direction) { 496 case 0x8000: 497 addressString += " direction forward"; 498 break; 499 case 0xC000: 500 addressString += " direction reverse"; 501 break; 502 default: 503 addressString += " direction unknown"; 504 } 505 } 506 return addressString; 507 } 508 509 // address value is the 16 bits of the two bytes containing the 510 // address. The most significant two bits represent the direction. 511 DccLocoAddress getCanDetectorLocoAddress(int addressValue) { 512 if(!isCanDetectorMessage()) { 513 return null; 514 } 515 if(addressValue==0) { 516 return null; 517 } else { 518 int locoAddress = (0x3FFF&addressValue); 519 return new DccLocoAddress(locoAddress,locoAddress>=100); 520 } 521 } 522 523 /** 524 * @return the can Detector Message type or -1 if not a can detector message. 525 */ 526 public int canDetectorMessageType() { 527 if(isCanDetectorMessage()){ 528 return getElement(9) & 0xFF; 529 } 530 return -1; 531 } 532 533 /** 534 * @return true if the reply is for a CAN detector and the type is 0x01 535 */ 536 public boolean isCanSensorMessage(){ 537 return isCanDetectorMessage() && canDetectorMessageType() == 0x01; 538 } 539 540 /** 541 * @return true if the reply is for a CAN detector and the type is 0x01 542 */ 543 public boolean isCanReporterMessage(){ 544 int type = canDetectorMessageType(); 545 return isCanDetectorMessage() && type >= 0x11 && type<= 0x1f; 546 } 547 548}