001package jmri.jmrix.roco.z21; 002 003import jmri.jmrix.AbstractMRMessage; 004 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 015import jmri.util.StringUtil; 016 017/** 018 * Class for messages in the z21/Z21 protocol. 019 * 020 * Messages have the following format: 2 bytes data length. 2 bytes op code. n 021 * bytes data. 022 * 023 * All numeric values are stored in little endian format. 024 * 025 * Carries a sequence of characters, with accessors. 026 * 027 * @author Bob Jacobsen Copyright (C) 2003 028 * @author Paul Bender Copyright (C) 2014 029 */ 030public class Z21Message extends AbstractMRMessage { 031 032 public Z21Message() { 033 super(); 034 setBinary(true); 035 } 036 037 // create a new one 038 public Z21Message(int i) { 039 this(); 040 if (i < 4) { // minimum length is 2 bytes of length, 2 bytes of opcode. 041 log.error("invalid length in call to ctor"); 042 } 043 _nDataChars = i; 044 _dataChars = new int[i]; 045 setLength(i); 046 } 047 048 // from an XpressNet message (used for protocol tunneling) 049 public Z21Message(jmri.jmrix.lenz.XNetMessage m) { 050 this(m.getNumDataElements() + 4); 051 this.setOpCode(0x0040); 052 for (int i = 0; i < m.getNumDataElements(); i++) { 053 setElement(i + 4, m.getElement(i)); 054 } 055 } 056 057 // from an LocoNetNet message (used for protocol tunneling) 058 public Z21Message(jmri.jmrix.loconet.LocoNetMessage m) { 059 this(m.getNumDataElements() + 4); 060 if ((m.getOpCode() & 0x08) == 0x00) { 061 mReplyExpected = false; 062 } 063 this.setOpCode(0x00A2); 064 for (int i = 0; i < m.getNumDataElements(); i++) { 065 setElement(i + 4, m.getElement(i)); 066 } 067 } 068 069 /** 070 * This ctor interprets the String as the exact sequence to send, 071 * byte-for-byte. 072 * 073 * @param m message string. 074 */ 075 public Z21Message(String m) { 076 super(m); 077 setBinary(true); 078 // gather bytes in result 079 byte[] b = jmri.util.StringUtil.bytesFromHexString(m); 080 if (b.length == 0) { 081 // no such thing as a zero-length message 082 _nDataChars = 0; 083 _dataChars = null; 084 return; 085 } 086 _nDataChars = b.length; 087 _dataChars = new int[_nDataChars]; 088 for (int i = 0; i < b.length; i++) { 089 setElement(i, b[i]); 090 } 091 } 092 093 /** 094 * This ctor interprets the byte array as a sequence of characters to send. 095 * @deprecated 5.13.5, unused, requires further development. 096 * @param a Array of bytes to send 097 * @param l unused. 098 */ 099 @Deprecated( since="5.13.5", forRemoval=true) 100 public Z21Message(byte[] a, int l) { 101 // super(String.valueOf(a)); // Spotbug toString on array 102 // requires further development to produce correct values for hardware type. 103 super(StringUtil.hexStringFromBytes(a).replaceAll("\\s", "")); 104 setBinary(true); 105 } 106 107 boolean mReplyExpected = true; 108 @Override 109 public boolean replyExpected() { 110 return mReplyExpected; 111 } 112 113 @Override 114 public void setOpCode(int i) { 115 _dataChars[2] = (i & 0x00ff); 116 _dataChars[3] = ((i & 0xff00) >> 8); 117 } 118 119 @Override 120 public int getOpCode() { 121 return ( (0xff & _dataChars[2]) + ((0xff & _dataChars[3]) << 8)); 122 } 123 124 public void setLength(int i) { 125 _dataChars[0] = (i & 0x00ff); 126 _dataChars[1] = ((i & 0xff00) >> 8); 127 } 128 129 public int getLength() { 130 return (_dataChars[0] + (_dataChars[1] << 8)); 131 } 132 133 /* 134 * package protected method to get the _dataChars buffer as bytes. 135 * @return byte array containing the low order bits of the integer 136 * values in _dataChars. 137 */ 138 byte[] getBuffer() { 139 byte[] byteData = new byte[_dataChars.length]; 140 for (int i = 0; i < _dataChars.length; i++) { 141 byteData[i] = (byte) (0x00ff & _dataChars[i]); 142 } 143 return byteData; 144 } 145 146 /* 147 * canned messages 148 */ 149 150 /* 151 * @return z21 message for serial number request. 152 */ 153 public static Z21Message getSerialNumberRequestMessage() { 154 Z21Message retval = new Z21Message(4); 155 retval.setElement(0, 0x04); 156 retval.setElement(1, 0x00); 157 retval.setElement(2, 0x10); 158 retval.setElement(3, 0x00); 159 return retval; 160 } 161 162 /* 163 * @return z21 message for a hardware information request. 164 */ 165 public static Z21Message getLanGetHardwareInfoRequestMessage() { 166 Z21Message retval = new Z21Message(4); 167 retval.setElement(0, 0x04); 168 retval.setElement(1, 0x00); 169 retval.setElement(2, 0x1A); 170 retval.setElement(3, 0x00); 171 return retval; 172 } 173 174 /* 175 * @return z21 message for LAN_LOGOFF request. 176 */ 177 public static Z21Message getLanLogoffRequestMessage() { 178 Z21Message retval = new Z21Message(4){ 179 @Override 180 public boolean replyExpected() { 181 return false; // Loging off generates no reply. 182 } 183 }; 184 retval.setElement(0, 0x04); 185 retval.setElement(1, 0x00); 186 retval.setElement(2, 0x30); 187 retval.setElement(3, 0x00); 188 return retval; 189 } 190 191 /** 192 * @return z21 message for LAN_GET_BROADCAST_FLAGS request. 193 */ 194 public static Z21Message getLanGetBroadcastFlagsRequestMessage() { 195 Z21Message retval = new Z21Message(4); 196 retval.setElement(0, 0x04); 197 retval.setElement(1, 0x00); 198 retval.setElement(2, 0x51); 199 retval.setElement(3, 0x00); 200 return retval; 201 } 202 203 /** 204 * Set the broadcast flags as described in section 2.16 of the 205 * Roco Z21 Protocol Manual. 206 * <p> 207 * Brief descriptions of the flags are as follows (losely 208 * translated from German with the aid of google translate). 209 * <ul> 210 * <li>0x00000001 send XpressNet related information (track 211 * power on/off, programming mode, short circuit, broadcast stop, 212 * locomotive information, turnout information).</li> 213 * <li>0x00000002 send data changes that occur on the RMBUS.</li> 214 * <li>0x00000004 (deprecated by Roco) send Railcom Data</li> 215 * <li>0x00000100 send changes in system state (such as track voltage) 216 * <li>0x00010000 send changes to locomotives on XpressNet (must also have 217 * 0x00000001 set.</li> 218 * <li>0x01000000 forward LocoNet data to the client. Does not send 219 * Locomotive or turnout data.</li> 220 * <li>0x02000000 send Locomotive specific LocoNet data to the client.</li> 221 * <li>0x04000000 send Turnout specific LocoNet data to the client.</li> 222 * <li>0x08000000 send Occupancy information from LocoNet to the client</li> 223 * <li>0x00040000 Automatically send updates for Railcom data to the client</li> 224 * <li>0x00080000 send can detector messages to the client</li> 225 * </ul> 226 * 227 * @param flags integer representing the flags (32 bits). 228 * @return z21 message for LAN_SET_BROADCAST_FLAGS request. 229 */ 230 public static Z21Message getLanSetBroadcastFlagsRequestMessage(int flags) { 231 Z21Message retval = new Z21Message(8){ 232 @Override 233 public boolean replyExpected() { 234 return false; // setting the broadcast flags generates 235 // no reply. 236 } 237 }; 238 retval.setElement(0, 0x08); 239 retval.setElement(1, 0x00); 240 retval.setElement(2, 0x50); 241 retval.setElement(3, 0x00); 242 retval.setElement(4, (flags & 0x000000ff) ); 243 retval.setElement(5, (flags & 0x0000ff00)>>8 ); 244 retval.setElement(6, (flags & 0x00ff0000)>>16 ); 245 retval.setElement(7, (flags & 0xff000000)>>24 ); 246 return retval; 247 } 248 249 250 /** 251 * @return z21 message for LAN_RAILCOM_GETDATA request. 252 */ 253 public static Z21Message getLanRailComGetDataRequestMessage() { 254 return getLanRailComGetDataRequestMessage(0); // address 0 causes the Z21 to search for the next address. 255 } 256 257 /** 258 * @param address the address of the locomotive to request RailCom data for. 259 * @return z21 message for LAN_RAILCOM_GETDATA request. 260 */ 261 public static Z21Message getLanRailComGetDataRequestMessage(int address) { 262 Z21Message retval = new Z21Message(7); 263 retval.setElement(0, 0x07); 264 retval.setElement(1, 0x00); 265 retval.setElement(2, 0x89); 266 retval.setElement(3, 0x00); 267 retval.setElement(4, 0x01); 268 retval.setElement(5, address & 0xff); 269 retval.setElement(6, (address & 0xff00)>>8); 270 return retval; 271 } 272 273 /** 274 * @return z21 message for LAN_SYSTEMSTATE_GETDATA 275 */ 276 public static Z21Message getLanSystemStateDataChangedRequestMessage(){ 277 Z21Message retval = new Z21Message(4); 278 retval.setElement(0, 0x04); 279 retval.setElement(1, 0x00); 280 retval.setElement(2, 0x85); 281 retval.setElement(3, 0x00); 282 return retval; 283 } 284 285 private static List<Z21MessageFormatter> formatterList = new ArrayList<>(); 286 287 @Override 288 public String toMonitorString() { 289 if(formatterList.isEmpty()) { 290 try { 291 292 Reflections reflections = new Reflections("jmri.jmrix.roco.z21.messageformatters"); 293 Set<Class<? extends Z21MessageFormatter>> f = reflections.getSubTypesOf(Z21MessageFormatter.class); 294 for (Class<?> c : f) { 295 log.debug("Found formatter: {}", f.getClass().getName()); 296 Constructor<?> ctor = c.getConstructor(); 297 formatterList.add((Z21MessageFormatter) ctor.newInstance()); 298 } 299 } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | 300 IllegalArgumentException | InvocationTargetException e) { 301 log.error("Error instantiating formatter", e); 302 } 303 } 304 305 return formatterList.stream() 306 .filter(f -> f.handlesMessage(this)) 307 .findFirst().map(f -> f.formatMessage(this)) 308 .orElse(this.toString()); 309 } 310 311 // handle LocoNet messages tunneled in Z21 messages 312 boolean isLocoNetTunnelMessage() { 313 return( getOpCode() == 0x00A2); 314 } 315 316 boolean isLocoNetDispatchMessage() { 317 return (getOpCode() == 0x00A3); 318 } 319 320 boolean isLocoNetDetectorMessage() { 321 return (getOpCode() == 0x00A4); 322 } 323 324 public jmri.jmrix.loconet.LocoNetMessage getLocoNetMessage() { 325 jmri.jmrix.loconet.LocoNetMessage lnr = null; 326 if (isLocoNetTunnelMessage()) { 327 int i = 4; 328 lnr = new jmri.jmrix.loconet.LocoNetMessage(getLength()-4); 329 for (; i < getLength(); i++) { 330 lnr.setElement(i - 4, getElement(i)); 331 } 332 } 333 return lnr; 334 } 335 336 /** 337 * @param group the RM Bus group number to request. 338 * @return z21 message for LAN_RMBUS_GETDATA 339 */ 340 public static Z21Message getLanRMBusGetDataRequestMessage(int group){ 341 if(group!=0 && group!=1){ 342 throw new IllegalArgumentException("RMBus Group not 0 or 1"); 343 } 344 Z21Message retval = new Z21Message(5); 345 retval.setElement(0, 0x04); 346 retval.setElement(1, 0x00); 347 retval.setElement(2, 0x81); 348 retval.setElement(3, 0x00); 349 retval.setElement(4, (group & 0xff)); 350 return retval; 351 } 352 353 /** 354 * @param address the RM Bus address to write. 355 * @return z21 message for LAN_RMBUS_PROGRAMMODULE 356 */ 357 public static Z21Message getLanRMBusProgramModuleMessage(int address){ 358 if(address>20){ 359 throw new IllegalArgumentException("RMBus Address > 20"); 360 } 361 Z21Message retval = new Z21Message(5); 362 retval.setElement(0, 0x05); 363 retval.setElement(1, 0x00); 364 retval.setElement(2, 0x82); 365 retval.setElement(3, 0x00); 366 retval.setElement(4, (address & 0xff)); 367 return retval; 368 } 369 370 // handle CAN Feedback/Railcom Messages 371 boolean isCanDetectorMessage() { 372 return (getOpCode() == 0x00C4); 373 } 374 375 /** 376 * @param address CAN NetworkID of the module to request data from. 377 * @return z21 message for LAN_CAN_DETECTOR request message 378 */ 379 public static Z21Message getLanCanDetector(int address){ 380 Z21Message retval = new Z21Message(7); 381 retval.setElement(0, 0x07); 382 retval.setElement(1, 0x00); 383 retval.setElement(2, 0xC4); 384 retval.setElement(3, 0x00); 385 retval.setElement(4, 0x00);// type, currently fixed. 386 retval.setElement(5, (address & 0xff)); 387 retval.setElement(6, ((address & 0xff00)>>8)); 388 return retval; 389 } 390 391 /** 392 * @param address CAN NetworkID of the module to request data from. 393 * @return z21 message for LAN_CAN_GET_DESCRIPTION request message 394 */ 395 public static Z21Message getLanCanGetDescription(int address) { 396 Z21Message retval = new Z21Message(6); 397 retval.setElement(0, 0x06); 398 retval.setElement(1, 0x00); 399 retval.setElement(2, 0xC8); 400 retval.setElement(3, 0x00); 401 retval.setElement(4, (address & 0xff)); 402 retval.setElement(5, ((address & 0xff00)>>8)); 403 return retval; 404 } 405 406 /** 407 * @param address CAN NetworkID of the module to request data from. 408 * @param description description for the CAN Module 409 * @return z21 message for LAN_CAN_SET_DESCRIPTION request message 410 */ 411 public static Z21Message getLanCanSetDescription(int address, String description) { 412 Z21Message retval = new Z21Message(22); 413 retval.setElement(0, 0x16); 414 retval.setElement(1, 0x00); 415 retval.setElement(2, 0xC9); 416 retval.setElement(3, 0x00); 417 retval.setElement(4, (address & 0xff)); 418 retval.setElement(5, ((address & 0xff00)>>8)); 419 for (int j = 0; j < 16; j++) { 420 if (j < description.length()) { 421 retval.setElement(6 + j, description.charAt(j)); 422 } else { 423 retval.setElement(6 + j, '\0'); // pad with nulls if description is too short. 424 } 425 } 426 return retval; 427 } 428 429 430 /** 431 * @param address CAN NetworkID of the module to set. 432 * @param powerSet track power state to set (0x00 for all on, 0xFF for all off.). 433 * @return z21 message for LAN_CAN_SET_BOOSTER_TRACK_POWER request message 434 */ 435 public static Z21Message getLanCanSetBoosterTrackPower(int address, int powerSet) { 436 Z21Message retval = new Z21Message(7); 437 retval.setElement(0, 0x07); 438 retval.setElement(1, 0x00); 439 retval.setElement(2, 0xCB); 440 retval.setElement(3, 0x00); 441 retval.setElement(4, (address & 0xff)); 442 retval.setElement(5, ((address & 0xff00)>>8)); 443 retval.setElement(6, (powerSet & 0xff)); 444 return retval; 445 } 446 447 private static final Logger log = LoggerFactory.getLogger(Z21Message.class); 448 449}