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 Z21Message retval = new Z21Message(4); 255 retval.setElement(0, 0x04); 256 retval.setElement(1, 0x00); 257 retval.setElement(2, 0x89); 258 retval.setElement(3, 0x00); 259 return retval; 260 } 261 262 /** 263 * @return z21 message for LAN_SYSTEMSTATE_GETDATA 264 */ 265 public static Z21Message getLanSystemStateDataChangedRequestMessage(){ 266 Z21Message retval = new Z21Message(4); 267 retval.setElement(0, 0x04); 268 retval.setElement(1, 0x00); 269 retval.setElement(2, 0x85); 270 retval.setElement(3, 0x00); 271 return retval; 272 } 273 274 private static List<Z21MessageFormatter> formatterList = new ArrayList<>(); 275 276 @Override 277 public String toMonitorString() { 278 if(formatterList.isEmpty()) { 279 try { 280 281 Reflections reflections = new Reflections("jmri.jmrix.roco.z21.messageformatters"); 282 Set<Class<? extends Z21MessageFormatter>> f = reflections.getSubTypesOf(Z21MessageFormatter.class); 283 for (Class<?> c : f) { 284 log.debug("Found formatter: {}", f.getClass().getName()); 285 Constructor<?> ctor = c.getConstructor(); 286 formatterList.add((Z21MessageFormatter) ctor.newInstance()); 287 } 288 } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | 289 IllegalArgumentException | InvocationTargetException e) { 290 log.error("Error instantiating formatter", e); 291 } 292 } 293 294 return formatterList.stream() 295 .filter(f -> f.handlesMessage(this)) 296 .findFirst().map(f -> f.formatMessage(this)) 297 .orElse(this.toString()); 298 } 299 300 // handle LocoNet messages tunneled in Z21 messages 301 boolean isLocoNetTunnelMessage() { 302 return( getOpCode() == 0x00A2); 303 } 304 305 boolean isLocoNetDispatchMessage() { 306 return (getOpCode() == 0x00A3); 307 } 308 309 boolean isLocoNetDetectorMessage() { 310 return (getOpCode() == 0x00A4); 311 } 312 313 public jmri.jmrix.loconet.LocoNetMessage getLocoNetMessage() { 314 jmri.jmrix.loconet.LocoNetMessage lnr = null; 315 if (isLocoNetTunnelMessage()) { 316 int i = 4; 317 lnr = new jmri.jmrix.loconet.LocoNetMessage(getLength()-4); 318 for (; i < getLength(); i++) { 319 lnr.setElement(i - 4, getElement(i)); 320 } 321 } 322 return lnr; 323 } 324 325 /** 326 * @param group the RM Bus group number to request. 327 * @return z21 message for LAN_RMBUS_GETDATA 328 */ 329 public static Z21Message getLanRMBusGetDataRequestMessage(int group){ 330 if(group!=0 && group!=1){ 331 throw new IllegalArgumentException("RMBus Group not 0 or 1"); 332 } 333 Z21Message retval = new Z21Message(5); 334 retval.setElement(0, 0x04); 335 retval.setElement(1, 0x00); 336 retval.setElement(2, 0x81); 337 retval.setElement(3, 0x00); 338 retval.setElement(4, (group & 0xff)); 339 return retval; 340 } 341 342 /** 343 * @param address the RM Bus address to write. 344 * @return z21 message for LAN_RMBUS_PROGRAMMODULE 345 */ 346 public static Z21Message getLanRMBusProgramModuleMessage(int address){ 347 if(address>20){ 348 throw new IllegalArgumentException("RMBus Address > 20"); 349 } 350 Z21Message retval = new Z21Message(5); 351 retval.setElement(0, 0x05); 352 retval.setElement(1, 0x00); 353 retval.setElement(2, 0x82); 354 retval.setElement(3, 0x00); 355 retval.setElement(4, (address & 0xff)); 356 return retval; 357 } 358 359 // handle CAN Feedback/Railcom Messages 360 boolean isCanDetectorMessage() { 361 return (getOpCode() == 0x00C4); 362 } 363 364 /** 365 * @param address CAN NetworkID of the module to request data from. 366 * @return z21 message for LAN_CAN_DETECTOR request message 367 */ 368 public static Z21Message getLanCanDetector(int address){ 369 Z21Message retval = new Z21Message(7); 370 retval.setElement(0, 0x07); 371 retval.setElement(1, 0x00); 372 retval.setElement(2, 0xC4); 373 retval.setElement(3, 0x00); 374 retval.setElement(4, 0x00);// type, currently fixed. 375 retval.setElement(5, (address & 0xff)); 376 retval.setElement(6, ((address & 0xff00)>>8)); 377 return retval; 378 } 379 380 private static final Logger log = LoggerFactory.getLogger(Z21Message.class); 381 382}