001package jmri.jmrix; 002 003import java.util.Objects; 004import javax.annotation.Nonnull; 005import jmri.util.StringUtil; 006import org.slf4j.Logger; 007import org.slf4j.LoggerFactory; 008 009/** 010 * Abstract base class for messages in a message/reply protocol. 011 * <p> 012 * Carries a sequence of characters, with accessors. 013 * 014 * @author Bob Jacobsen Copyright (C) 2003 015 */ 016abstract public class AbstractMRMessage extends AbstractMessage { 017 018 /** 019 * Create a new AbstractMRMessage instance. 020 */ 021 public AbstractMRMessage() { 022 setBinary(false); 023 setNeededMode(AbstractMRTrafficController.NORMALMODE); 024 setTimeout(SHORT_TIMEOUT); // default value is the short timeout 025 setRetries(0); // default to no retries 026 } 027 028 /** 029 * Create a new AbstractMRMessage instance of a given byte size. 030 * 031 * @param i number of elements in message 032 */ 033 public AbstractMRMessage(int i) { 034 this(); 035 if (i < 1) { 036 log.error("invalid length {} in call to ctor", i); 037 throw new IllegalArgumentException("invalid length in call to ctor"); 038 } 039 _nDataChars = i; 040 _dataChars = new int[i]; 041 } 042 043 /** 044 * Copy an AbstractMRMessage to a new instance. 045 * 046 * @param m the message to copy 047 */ 048 public AbstractMRMessage(@Nonnull AbstractMRMessage m) { 049 this(); 050 Objects.requireNonNull(m, "copy ctor of null message"); 051 _nDataChars = m._nDataChars; 052 _dataChars = new int[_nDataChars]; 053 System.arraycopy(m._dataChars, 0, _dataChars, 0, _nDataChars); 054 setTimeout(m.getTimeout()); 055 setRetries(m.getRetries()); 056 setNeededMode(m.getNeededMode()); 057 } 058 059 /** 060 * Create a new Message instance from a string. 061 * 062 * @param s String to use as message content 063 */ 064 public AbstractMRMessage(String s) { 065 this(s.length()); 066 for (int i = 0; i < _nDataChars; i++) { 067 _dataChars[i] = s.charAt(i); 068 } 069 } 070 071 public void setOpCode(int i) { 072 _dataChars[0] = i; 073 } 074 075 public int getOpCode() { 076 try { 077 return _dataChars[0]; 078 } catch(ArrayIndexOutOfBoundsException e) { 079 return 0; 080 } 081 } 082 083 public String getOpCodeHex() { 084 return "0x" + Integer.toHexString(getOpCode()); 085 } 086 087 // accessors to the bulk data 088 089 // state info 090 private int mNeededMode; 091 092 /** 093 * Set a needed mode. 094 * Final so that it can be called in constructors. 095 * @param pMode required mode value. 096 */ 097 final public void setNeededMode(int pMode) { 098 mNeededMode = pMode; 099 } 100 101 /** 102 * Get needed mode. 103 * Final so that it can be called in constructors. 104 * @return mNeededMode required mode value. 105 */ 106 final public int getNeededMode() { 107 return mNeededMode; 108 } 109 110 /** 111 * Is a reply expected to this message? 112 * <p> 113 * By default, a reply is expected to every message; either a reply or a 114 * timeout is needed before the next message can be sent. 115 * <p> 116 * If this returns false, the transmit queue will immediately go on to 117 * transmit the next message (if any). 118 * @return true by default in Abstract MR message. 119 */ 120 public boolean replyExpected() { 121 return true; 122 } 123 124 // mode accessors 125 private boolean _isBinary; 126 127 /** 128 * Get if is binary. 129 * Final so that it can be called in constructors. 130 * @return true if binary, else false. 131 */ 132 final public boolean isBinary() { 133 return _isBinary; 134 } 135 136 /** 137 * Set if Binary. 138 * final so that it can be called in constructors. 139 * @param b true if binary, else false. 140 */ 141 final public void setBinary(boolean b) { 142 _isBinary = b; 143 } 144 145 /** 146 * Minimum timeout that's acceptable. 147 * <p> 148 * Also used as default for normal operations. Don't shorten this "to make 149 * recovery faster", as sometimes <i>internal</i> delays can slow processing 150 * down. 151 * <p> 152 * Units are milliseconds. 153 */ 154 static protected final int SHORT_TIMEOUT = 2000; 155 static protected final int LONG_TIMEOUT = 60000; // e.g. for programming options 156 157 private int mTimeout; // in milliseconds 158 159 /** 160 * Set Timeout. 161 * Final so that it can be called in constructors. 162 * @param t timeout value. 163 */ 164 final public void setTimeout(int t) { 165 mTimeout = t; 166 } 167 168 /** 169 * Get Timeout. 170 * Final so that it can be called in constructors. 171 * @return timeout value. 172 */ 173 final public int getTimeout() { 174 return mTimeout; 175 } 176 177 /* For some systems, we want to retry sending a message if the port 178 isn't ready for them. */ 179 private int mRetries = 0; // number of retries, default = 0; 180 181 /** 182 * Set number of retries. 183 * Final so that it can be called in constructors 184 * @param i number of retries, actual value to set, not an increment. 185 */ 186 final public void setRetries(int i) { 187 mRetries = i; 188 } 189 190 /** 191 * Get number of retries. 192 * final so that it can be called in constructors 193 * @return number of retries count. 194 */ 195 final public int getRetries() { 196 return mRetries; 197 } 198 199 // display format 200 201 // contents (private) 202 public void addIntAsThree(int val, int offset) { 203 String s = "" + val; 204 if (s.length() != 3) { 205 s = "0" + s; // handle <10 206 } 207 if (s.length() != 3) { 208 s = "0" + s; // handle <100 209 } 210 setElement(offset, s.charAt(0)); 211 setElement(offset + 1, s.charAt(1)); 212 setElement(offset + 2, s.charAt(2)); 213 } 214 215 /** 216 * Put an int value into the message as 217 * two ASCII upper-case hex characters. 218 * @param val value to convert. 219 * @param offset offset in message. 220 */ 221 public void addIntAsTwoHex(int val, int offset) { 222 String s = ("" + Integer.toHexString(val)).toUpperCase(); 223 if (s.length() < 2) { 224 s = "0" + s; // handle one digit 225 } 226 if (s.length() > 2) { 227 log.error("can't add as two hex digits: {}", s); 228 } 229 setElement(offset, s.charAt(0)); 230 setElement(offset + 1, s.charAt(1)); 231 } 232 233 /** 234 * Put an int value into the message as 235 * three ASCII upper-case hex characters. 236 * @param val value to convert. 237 * @param offset offset in message. 238 */ 239 public void addIntAsThreeHex(int val, int offset) { 240 String s = ("" + Integer.toHexString(val)).toUpperCase(); 241 if (s.length() > 3) { 242 log.error("can't add as three hex digits: {}", s); 243 } 244 if (s.length() != 3) { 245 s = "0" + s; 246 } 247 if (s.length() != 3) { 248 s = "0" + s; 249 } 250 setElement(offset, s.charAt(0)); 251 setElement(offset + 1, s.charAt(1)); 252 setElement(offset + 2, s.charAt(2)); 253 } 254 255 /** 256 * Put an int value into the message as 257 * four ASCII upper-case hex characters. 258 * @param val value to convert. 259 * @param offset offset in message. 260 */ 261 public void addIntAsFourHex(int val, int offset) { 262 String s = ("" + Integer.toHexString(val)).toUpperCase(); 263 if (s.length() > 4) { 264 log.error("can't add as three hex digits: {}", s); 265 } 266 if (s.length() != 4) { 267 s = "0" + s; 268 } 269 if (s.length() != 4) { 270 s = "0" + s; 271 } 272 if (s.length() != 4) { 273 s = "0" + s; 274 } 275 setElement(offset, s.charAt(0)); 276 setElement(offset + 1, s.charAt(1)); 277 setElement(offset + 2, s.charAt(2)); 278 setElement(offset + 3, s.charAt(3)); 279 } 280 281 @Override 282 public String toString() { 283 String s = ""; 284 for (int i = 0; i < _nDataChars; i++) { 285 if (_isBinary) { 286 if (i != 0) { 287 s += " "; 288 } 289 s = StringUtil.appendTwoHexFromInt(_dataChars[i] & 255, s); 290 } else { 291 s += (char) _dataChars[i]; 292 } 293 } 294 return s; 295 } 296 297 private final static Logger log = LoggerFactory.getLogger(AbstractMRMessage.class); 298 299}