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