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