001package jmri.jmrix.roco.z21; 002 003import jmri.jmrix.AbstractMRReply; 004import jmri.DccLocoAddress; 005import org.reflections.Reflections; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009import java.lang.reflect.Constructor; 010import java.lang.reflect.InvocationTargetException; 011import java.util.ArrayList; 012import java.util.List; 013import java.util.Set; 014 015/** 016 * Class for replies in the z21/Z21 protocol. 017 * <p> 018 * Replies are of the format: 2 bytes length 2 bytes opcode n bytes data 019 * <p> 020 * numeric data is sent in little endian format. 021 * 022 * @author Bob Jacobsen Copyright (C) 2003 023 * @author Paul Bender Copyright (C) 2014 024 */ 025public class Z21Reply extends AbstractMRReply { 026 027 private static final String WRONG_REPLY_TYPE = "Wrong Reply Type"; 028 029 /** 030 * Create a new one. 031 */ 032 public Z21Reply() { 033 super(); 034 setBinary(true); 035 } 036 037 /** 038 * This ctor interprets the byte array as a sequence of characters to send. 039 * 040 * @param a Array of bytes to send. 041 * @param l length of reply. 042 */ 043 public Z21Reply(byte[] a, int l) { 044 super(); 045 _nDataChars = l; 046 setBinary(true); 047 for (int i = 0; i < _nDataChars; i++) { 048 _dataChars[i] = a[i]; 049 } 050 } 051 052 // keep track of length 053 @Override 054 public void setElement(int n, int v) { 055 _dataChars[n] = (char) v; 056 _nDataChars = Math.max(_nDataChars, n + 1); 057 } 058 059 /** 060 * Get an integer representation of a BCD value. 061 * 062 * @param n byte in message to convert 063 * @return Integer value of BCD byte. 064 */ 065 public Integer getElementBCD(int n) { 066 return Integer.decode(Integer.toHexString(getElement(n))); 067 } 068 069 @Override 070 public void setOpCode(int i) { 071 _dataChars[2] = (char) (i & 0x00ff); 072 _dataChars[3] = (char) ((i & 0xff00) >> 8); 073 _nDataChars = Math.max(_nDataChars, 4); //smallest reply is of length 4. 074 } 075 076 @Override 077 public int getOpCode() { 078 return (0xff&_dataChars[2]) + ((0xff&_dataChars[3]) << 8); 079 } 080 081 public void setLength(int i) { 082 _dataChars[0] = (char) (i & 0x00ff); 083 _dataChars[1] = (char) ((i & 0xff00) >> 8); 084 _nDataChars = Math.max(_nDataChars, i); 085 } 086 087 public int getLength() { 088 return (0xff & _dataChars[0] ) + ((0xff & _dataChars[1]) << 8); 089 } 090 091 @Override 092 protected int skipPrefix(int index) { 093 return 0; 094 } 095 096 private static List<Z21MessageFormatter> formatterList = new ArrayList<>(); 097 098 @Override 099 public String toMonitorString() { 100 if(formatterList.isEmpty()) { 101 try { 102 Reflections reflections = new Reflections("jmri.jmrix.roco.z21.messageformatters"); 103 Set<Class<? extends Z21MessageFormatter>> f = reflections.getSubTypesOf(Z21MessageFormatter.class); 104 for (Class<?> c : f) { 105 log.debug("Found formatter: {}", f.getClass().getName()); 106 Constructor<?> ctor = c.getConstructor(); 107 formatterList.add((Z21MessageFormatter) ctor.newInstance()); 108 } 109 } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | 110 IllegalArgumentException | InvocationTargetException e) { 111 log.error("Error instantiating formatter", e); 112 } 113 } 114 115 return formatterList.stream() 116 .filter(f -> f.handlesMessage(this)) 117 .findFirst().map(f -> f.formatMessage(this)) 118 .orElse(this.toString()); 119 } 120 121 // handle XpressNet replies tunneled in Z21 messages 122 boolean isXPressNetTunnelMessage() { 123 return (getOpCode() == 0x0040); 124 } 125 126 public Z21XNetReply getXNetReply() { 127 Z21XNetReply xnr = null; 128 if (isXPressNetTunnelMessage()) { 129 int i = 4; 130 xnr = new Z21XNetReply(); 131 for (; i < getLength(); i++) { 132 xnr.setElement(i - 4, getElement(i)); 133 } 134 if(( xnr.getElement(0) & 0x0F ) > ( xnr.getNumDataElements()+2) ){ 135 // there is at least one message from the Z21 that can be sent 136 // with fewer bytes than the XpressNet payload indicates it 137 // should have. Pad those messages with 0x00 bytes. 138 for(i=i-4;i<((xnr.getElement(0)&0x0F)+2);i++){ 139 xnr.setElement(i,0x00); 140 } 141 } 142 } 143 return xnr; 144 } 145 146 // handle RailCom data replies 147 boolean isRailComDataChangedMessage(){ 148 return (getOpCode() == 0x0088); 149 } 150 151 /** 152 * @return the number of RailCom entries in this message. 153 * the returned value is in the 0 to 19 range. 154 */ 155 public int getNumRailComDataEntries(){ 156 if(!this.isRailComDataChangedMessage()){ 157 return 0; // this isn't a RailCom message, so there are no entries. 158 } 159 // if this is a RailCom message, the length field is 160 // then the entries are n=(len-4)/13, per the Z21 protocol 161 // manual, section 8.1. Also, 0<=n<=19 162 return ((getLength() - 4)/13); 163 } 164 165 /** 166 * Get a locomotive address from an entry in a railcom message. 167 * 168 * @param n the entry to get the address from. 169 * @return the locomotive address for the specified entry. 170 */ 171 public DccLocoAddress getRailComLocoAddress(int n){ 172 int offset = 4+(n*13); // +4 to get past header 173 int address = Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 174 return new DccLocoAddress(address,address>=100); 175 } 176 177 /** 178 * Get the receive counter from an entry in a railcom message. 179 * 180 * @param n the entry to get the address from. 181 * @return the receive counter for the specified entry. 182 */ 183 public int getRailComRcvCount(int n){ 184 int offset = 6+(n*13); // +6 to get header and address. 185 return ((0xff&getElement(offset+3))<<24) + 186 ((0xff&(getElement(offset+2))<<16) + 187 ((0xff&getElement(offset+1))<<8) + 188 (0xff&(getElement(offset)))); 189 } 190 191 /** 192 * Get the error counter from an entry in a railcom message. 193 * 194 * @param n the entry to get the address from. 195 * @return the error counter for the specified entry. 196 */ 197 public int getRailComErrCount(int n){ 198 int offset = 10+(n*13); // +10 to get past header, address,and rcv count. 199 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 200 } 201 202 /** 203 * Get the speed value from an entry in a railcom message. 204 * 205 * @param n the entry to get the address from. 206 * @return the error counter for the specified entry. 207 */ 208 public int getRailComSpeed(int n){ 209 int options = getRailComOptions(n); 210 if(((options & 0x01) == 0x01) || ((options & 0x02) == 0x02)) { 211 int offset = 14+(n*13); //+14 to get past the options, 212 // and everything before the options. 213 return (0xff&(getElement(offset))); 214 } else { 215 return 0; 216 } 217 } 218 219 /** 220 * Get the options value from an entry in a railcom message. 221 * 222 * @param n the entry to get the address from. 223 * @return the options for the specified entry. 224 */ 225 public int getRailComOptions(int n){ 226 int offset = 13+(n*13); //+13 to get past the header, address, rcv 227 // counter, and reserved byte. 228 return (0xff&(getElement(offset))); 229 } 230 231 /** 232 * Get the Quality of Service value from an entry in a railcom message. 233 * 234 * @param n the entry to get the address from. 235 * @return the Quality of Service value for the specified entry. 236 */ 237 public int getRailComQos(int n){ 238 if((getRailComOptions(n) & 0x04) == 0x04 ) { 239 int offset = 15+(n*13); //+15 to get past the speed, 240 // and everything before the speed. 241 return (0xff&(getElement(offset))); 242 } else { 243 return 0; // if the QOS bit isn't set, there is no QOS attribute. 244 } 245 } 246 247 // handle System data replies 248 boolean isSystemDataChangedReply(){ 249 return (getOpCode() == 0x0084); 250 } 251 252 private void checkSystemDataChangeReply(){ 253 if(!isSystemDataChangedReply()){ 254 throw new IllegalArgumentException(WRONG_REPLY_TYPE); 255 } 256 } 257 258 /** 259 * Get the Main Track Current from the SystemStateDataChanged 260 * message. 261 * 262 * @return the current in mA. 263 */ 264 public int getSystemDataMainCurrent(){ 265 checkSystemDataChangeReply(); 266 int offset = 4; //skip the headers 267 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 268 } 269 270 /** 271 * Get the Programming Track Current from the SystemStateDataChanged 272 * message. 273 * 274 * @return the current in mA. 275 */ 276 public int getSystemDataProgCurrent(){ 277 checkSystemDataChangeReply(); 278 int offset = 6; //skip the headers 279 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 280 } 281 282 /** 283 * Get the Filtered Main Track Current from the SystemStateDataChanged 284 * message. 285 * 286 * @return the current in mA. 287 */ 288 public int getSystemDataFilteredMainCurrent(){ 289 checkSystemDataChangeReply(); 290 int offset = 8; //skip the headers 291 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 292 } 293 294 /** 295 * Get the Temperature from the SystemStateDataChanged 296 * message. 297 * 298 * @return the current in degrees C. 299 */ 300 public int getSystemDataTemperature(){ 301 checkSystemDataChangeReply(); 302 int offset = 10; //skip the headers 303 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 304 } 305 306 /** 307 * Get the Supply Voltage from the SystemStateDataChanged 308 * message. 309 * 310 * @return the current in mV. 311 */ 312 public int getSystemDataSupplyVoltage(){ 313 checkSystemDataChangeReply(); 314 int offset = 12; //skip the headers 315 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 316 } 317 318 /** 319 * Get the VCC (and track) Voltage from the SystemStateDataChanged 320 * message. 321 * 322 * @return the current in mV. 323 */ 324 public int getSystemDataVCCVoltage(){ 325 checkSystemDataChangeReply(); 326 int offset = 14; //skip the headers 327 return Z21MessageUtils.integer16BitFromOffeset(_dataChars,offset); 328 } 329 330 // handle LocoNet replies tunneled in Z21 messages 331 boolean isLocoNetTunnelMessage() { 332 switch (getOpCode()){ 333 case 0xA0: // LAN_LOCONET_Z21_RX 334 case 0xA1: // LAN_LOCONET_Z21_TX 335 case 0xA2: // LAN_LOCONET_FROM_LAN 336 return true; 337 default: 338 return false; 339 } 340 } 341 342 boolean isLocoNetDispatchMessage() { 343 return (getOpCode() == 0xA3); 344 } 345 346 boolean isLocoNetDetectorMessage() { 347 return (getOpCode() == 0xA4); 348 } 349 350 public jmri.jmrix.loconet.LocoNetMessage getLocoNetMessage() { 351 jmri.jmrix.loconet.LocoNetMessage lnr = null; 352 if (isLocoNetTunnelMessage()) { 353 int i = 4; 354 lnr = new jmri.jmrix.loconet.LocoNetMessage(getLength()-4); 355 for (; i < getLength(); i++) { 356 lnr.setElement(i - 4, getElement(i)); 357 } 358 } 359 return lnr; 360 } 361 362 // handle RMBus data replies 363 boolean isRMBusDataChangedReply(){ 364 return (getOpCode() == 0x0080); 365 } 366 367 // handle CAN Feedback/Railcom replies 368 boolean isCanDetectorMessage() { 369 return (getOpCode() == 0x00C4); 370 } 371 372 373 374 /** 375 * @return the can Detector Message type or -1 if not a can detector message. 376 */ 377 public int canDetectorMessageType() { 378 if(isCanDetectorMessage()){ 379 return getElement(9) & 0xFF; 380 } 381 return -1; 382 } 383 384 /** 385 * @return true if the reply is for a CAN detector and the type is 0x01 386 */ 387 public boolean isCanSensorMessage(){ 388 return isCanDetectorMessage() && canDetectorMessageType() == 0x01; 389 } 390 391 /** 392 * @return true if the reply is for a CAN detector and the type is 0x01 393 */ 394 public boolean isCanReporterMessage(){ 395 int type = canDetectorMessageType(); 396 return isCanDetectorMessage() && type >= 0x11 && type<= 0x1f; 397 } 398 399 private static final Logger log = LoggerFactory.getLogger(Z21Reply.class); 400}