001package jmri.jmrix.grapevine; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004import java.io.DataInputStream; 005import jmri.jmrix.AbstractMRListener; 006import jmri.jmrix.AbstractMRMessage; 007import jmri.jmrix.AbstractMRNodeTrafficController; 008import jmri.jmrix.AbstractMRReply; 009import org.slf4j.Logger; 010import org.slf4j.LoggerFactory; 011 012/** 013 * Convert Stream-based I/O to/from Grapevine serial messages. 014 * <p> 015 * The "SerialInterface" side sends/receives message objects. 016 * <p> 017 * The connection to a SerialPortController is via a pair of *Streams, which 018 * then carry sequences of characters for transmission. Note that this 019 * processing is handled in an independent thread. 020 * <p> 021 * This handles the state transitions, based on the necessary state in each 022 * message. 023 * <p> 024 * Handles initialization, polling, output, and input for multiple SerialNodes. 025 * 026 * @author Bob Jacobsen Copyright (C) 2003, 2006, 2008 027 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004 028 */ 029public class SerialTrafficController extends AbstractMRNodeTrafficController implements SerialInterface { 030 031 /** 032 * Create a new TrafficController instance. 033 * 034 * @param adaptermemo the associated SystemConnectionMemo 035 */ 036 public SerialTrafficController(GrapevineSystemConnectionMemo adaptermemo) { 037 super(); 038 mMemo = adaptermemo; 039 log.debug("creating a new GrapevineTrafficController object on {}", adaptermemo.getSystemPrefix()); 040 logDebug = log.isDebugEnabled(); 041 042 // set node range 043 init(0, 255); 044 045 // not polled at all, so allow unexpected messages, and 046 // use poll delay just to spread out startup 047 setAllowUnexpectedReply(true); 048 mWaitBeforePoll = 100; // default = 25 049 } 050 051 // have several debug statements in tight loops, e.g. every character; 052 // only want to check once 053 boolean logDebug = false; 054 055 /** 056 * Get minimum address of an Grapevine node as set on this TrafficController. 057 * @return minimum node address. 058 */ 059 public int getMinimumNodeAddress() { 060 return minNode; 061 } 062 063 // The methods to implement the SerialInterface 064 065 /** 066 * Make connection to existing PortController (adapter) object. 067 * 068 * @param p the Adapter we're connecting to 069 */ 070 public void connectPort(SerialPortController p) { 071 if (controller != null) { 072 log.warn("connectPort called when already connected"); 073 } else { 074 log.debug("connectPort invoked"); 075 } 076 //controller = p; 077 super.connectPort(p); 078 } 079 080 @Override 081 public synchronized void addSerialListener(SerialListener l) { 082 this.addListener(l); 083 } 084 085 @Override 086 public synchronized void removeSerialListener(SerialListener l) { 087 this.removeListener(l); 088 } 089 090 /** 091 * Set up for initialization of a Serial node. 092 * @param node node to initialize. 093 */ 094 public void initializeSerialNode(SerialNode node) { 095 synchronized (this) { 096 // find the node in the registered node list 097 for (int i = 0; i < getNumNodes(); i++) { 098 if (getNode(i) == node) { 099 // found node - set up for initialization 100 setMustInit(i, true); 101 return; 102 } 103 } 104 } 105 } 106 107 @Override 108 protected AbstractMRMessage enterProgMode() { 109 log.warn("enterProgMode doesn't make sense for Grapevine Serial"); 110 return null; 111 } 112 113 @Override 114 protected AbstractMRMessage enterNormalMode() { 115 return null; 116 } 117 118 /** 119 * Forward a SerialMessage to all registered SerialInterface listeners. 120 */ 121 @Override 122 protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) { 123 ((SerialListener) client).message((SerialMessage) m); 124 } 125 126 /** 127 * Forward a SerialReply to all registered SerialInterface listeners. 128 */ 129 @Override 130 protected void forwardReply(AbstractMRListener client, AbstractMRReply m) { 131 ((SerialListener) client).reply((SerialReply) m); 132 } 133 134 SerialSensorManager mSensorManager = null; 135 136 public void setSensorManager(SerialSensorManager m) { 137 mSensorManager = m; 138 // also register this to be notified 139 addSerialListener(m); 140 } 141 142 /** 143 * Handle initialization, output and polling for Grapevine from within the 144 * running thread. 145 */ 146 @Override 147 protected synchronized AbstractMRMessage pollMessage() { 148 // ensure validity of call 149 if (getNumNodes() <= 0) { 150 return null; 151 } 152 153 // move to a new node 154 curSerialNodeIndex++; 155 if (curSerialNodeIndex >= getNumNodes()) { 156 curSerialNodeIndex = 0; 157 } 158 // ensure that each node is initialized 159 if (getMustInit(curSerialNodeIndex)) { 160 setMustInit(curSerialNodeIndex, false); 161 SerialMessage m = (SerialMessage) (getNode(curSerialNodeIndex).createInitPacket()); 162 if (m != null) { 163 log.debug("send init message: {} to node {}", m.toString(), curSerialNodeIndex); 164 m.setTimeout(50); // wait for init to finish (milliseconds) 165 return m; 166 } // else fall through to continue 167 } 168 /* // send Output packet if needed */ 169 /* if (nodeArray[curSerialNodeIndex].mustSend()) { */ 170 /* log.debug("request write command to send"); */ 171 /* SerialMessage m = nodeArray[curSerialNodeIndex].createOutPacket(); */ 172 /* nodeArray[curSerialNodeIndex].resetMustSend(); */ 173 /* m.setTimeout(500); */ 174 /* return m; */ 175 /* } */ 176 /* // poll for Sensor input */ 177 /* if ( nodeArray[curSerialNodeIndex].getSensorsActive() ) { */ 178 /* // Some sensors are active for this node, issue poll */ 179 /* SerialMessage m = SerialMessage.getPoll( */ 180 /* nodeArray[curSerialNodeIndex].getNodeAddress()); */ 181 /* if (curSerialNodeIndex>=numNodes) curSerialNodeIndex = 0; */ 182 /* return m; */ 183 /* } */ 184 /* else { */ 185 /* // no Sensors (inputs) are active for this node */ 186 /* return null; */ 187 /* } */ 188 return null; 189 } 190 191 @Override 192 protected synchronized void handleTimeout(AbstractMRMessage m, AbstractMRListener l) { 193 // inform node, and if it resets then reinitialize 194 if (getNode(curSerialNodeIndex) != null) { 195 if (getNode(curSerialNodeIndex).handleTimeout(m, l)) { 196 setMustInit(curSerialNodeIndex, true); 197 } else { 198 log.warn("Timeout can't be handled due to missing node (index {})", curSerialNodeIndex); 199 } 200 } 201 } 202 203 @Override 204 protected synchronized void resetTimeout(AbstractMRMessage m) { 205 // inform node 206 getNode(curSerialNodeIndex).resetTimeout(m); 207 } 208 209 @Override 210 protected AbstractMRListener pollReplyHandler() { 211 return mSensorManager; 212 } 213 214 /** 215 * Forward a preformatted message to the actual interface. 216 */ 217 @Override 218 public void sendSerialMessage(SerialMessage m, SerialListener reply) { 219 if (m == null) { 220 log.debug("empty message"); 221 return; 222 } 223 log.debug("Grapevine SerialTrafficController sendMessage() {}", m.toString()); 224 sendMessage(m, reply); 225 } 226 227 /** 228 * Reference to the system connection memo. 229 */ 230 GrapevineSystemConnectionMemo mMemo = null; 231 232 /** 233 * Get access to the system connection memo associated with this traffic 234 * controller. 235 * 236 * @return associated systemConnectionMemo object 237 */ 238 public GrapevineSystemConnectionMemo getSystemConnectionMemo() { 239 return mMemo; 240 } 241 242 /** 243 * Set the system connection memo associated with this traffic controller. 244 * 245 * @param m associated systemConnectionMemo object 246 */ 247 public void setSystemConnectionMemo(GrapevineSystemConnectionMemo m) { 248 log.debug("GrapevineTrafficController set memo to {}", m.getUserName()); 249 mMemo = m; 250 } 251 252 @Override 253 protected AbstractMRReply newReply() { 254 return new SerialReply(); 255 } 256 257 @Override 258 protected boolean endOfMessage(AbstractMRReply msg) { 259 // our version of loadChars doesn't invoke this, so it shouldn't be called 260 log.error("Not using endOfMessage, should not be called"); 261 return false; 262 } 263 264 protected int currentAddr = -1; // at startup, can't match 265 266 int nextReplyLen = 4; 267 268 @Override 269 protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) { 270 nextReplyLen = ((SerialMessage) m).getReplyLen(); 271 super.forwardToPort(m, reply); 272 } 273 274 byte[] buffer = new byte[4]; 275 int state = 0; 276 277 @Override 278 protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException { 279 while (doNextStep(msg, istream)) { 280 } 281 } 282 283 /** 284 * Execute a state machine to parse messages from the input characters. May 285 * consume one or more than one character. 286 * 287 * @param msg Message to parse 288 * @param istream Source of data 289 * @return true when the message has been completely loaded 290 * @throws java.io.IOException from underlying operation 291 */ 292 @SuppressWarnings("fallthrough") 293 @SuppressFBWarnings(value = "SF_SWITCH_FALLTHROUGH") 294 boolean doNextStep(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException { 295 switch (state) { 296 case 0: 297 // get 1st char, check for address bit 298 buffer[0] = readByteProtected(istream); 299 log.debug("state 0, rcv {}", (buffer[0] & 0xFF)); 300 if ((buffer[0] & 0x80) == 0) { 301 log.warn("1st byte not address: {}", (buffer[0] & 0xFF)); 302 return true; // try again with next 303 } 304 state = 1; 305 // and continue anyway 306 case 1: 307 buffer[1] = readByteProtected(istream); 308 log.debug("state 1, rcv {}", (buffer[1] & 0xFF)); 309 if ((buffer[1] & 0x80) != 0) { 310 buffer[0] = buffer[1]; 311 state = 1; // use this as address and try again 312 log.warn("2nd byte HOB set: {}, going to state 1", (buffer[1] & 0xFF)); 313 return true; 314 } 315 state = 2; 316 // fall through 317 case 2: 318 // as a special case, see what happens if a short 319 // message is expected 320 if (nextReplyLen == 2) { 321 // we'll accept these two bytes as a reply 322 buffer[2] = 0; 323 buffer[3] = 0; 324 loadBuffer(msg); 325 ((SerialReply) msg).setNumDataElements(2); // flag short reply 326 nextReplyLen = 4; // only happens once 327 state = 0; 328 log.debug("Short message complete: {}", msg.toString()); 329 return false; // have received a message 330 } 331 // here for normal four byte message expected 332 buffer[2] = readByteProtected(istream); 333 log.debug("state 2, rcv {}", (buffer[2] & 0xFF)); 334 if (buffer[0] != buffer[2]) { 335 // no match, consider buffer[2] start of new message 336 log.warn("addresses don't match: {}, {}. going to state 1", (buffer[0] & 0xFF), (buffer[2] & 0xFF)); 337 buffer[0] = buffer[2]; 338 state = 1; 339 return true; 340 } 341 state = 3; 342 // fall through 343 case 3: 344 buffer[3] = readByteProtected(istream); 345 log.debug("state 3, rcv {}", (buffer[3] & 0xFF)); 346 if ((buffer[3] & 0x80) != 0) { // unexpected high bit 347 buffer[0] = buffer[3]; 348 state = 1; // use this as address and try again 349 log.warn("3rd byte HOB set: {}, going to state 1", (buffer[3] & 0xFF)); 350 return true; 351 } 352 // Check for "software version" command, error message; special 353 // cases with deliberately bad parity 354 boolean pollMsg = ((buffer[1] == buffer[3]) && (buffer[1] == 119)); 355 boolean errMsg = ((buffer[0] & 0xFF) == 0x80); 356 357 // check 'parity' 358 int parity = (buffer[0] & 0xF) + ((buffer[0] & 0x70) >> 4) 359 + ((buffer[1] * 2) & 0xF) + (((buffer[1] * 2) & 0xF0) >> 4) 360 + (buffer[3] & 0xF) + ((buffer[3] & 0x70) >> 4); 361 if (((parity & 0xF) != 0) && !pollMsg && !errMsg) { 362 log.warn("parity mismatch: {}, going to state 2 with content {}, {}", parity, (buffer[2] & 0xFF), (buffer[3] & 0xFF)); 363 buffer[0] = buffer[2]; 364 buffer[1] = buffer[3]; 365 state = 2; 366 return true; 367 } 368 // success! 369 loadBuffer(msg); 370 log.debug("Message complete: {}", msg.toString()); 371 state = 0; 372 return false; 373 default: 374 log.error("unexpected loadChars state: {}. go direct to state 0", state); 375 state = 0; 376 return true; 377 } 378 } 379 380 protected void loadBuffer(AbstractMRReply msg) { 381 msg.setElement(0, buffer[0]); 382 msg.setElement(1, buffer[1]); 383 msg.setElement(2, buffer[2]); 384 msg.setElement(3, buffer[3]); 385 } 386 387 @Override 388 protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException { 389 // does nothing 390 } 391 392 /** 393 * Add header to the outgoing byte stream. 394 * 395 * @param msg The output byte stream 396 * @return next location in the stream to fill 397 */ 398 @Override 399 protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) { 400 return 0; // Do nothing 401 } 402 403 /** 404 * Although this protocol doesn't use a trailer, we implement this method to 405 * set the expected reply address for this message. 406 * 407 * @param msg The output byte stream 408 * @param offset the first byte not yet used 409 * @param m the original message 410 */ 411 @Override 412 protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) { 413 currentAddr = ((SerialMessage) m).getAddr(); 414 return; 415 } 416 417 /** 418 * Determine how much many bytes the entire message will take, including 419 * space for header and trailer 420 * 421 * @param m The message to be sent 422 * @return Number of bytes 423 */ 424 @Override 425 protected int lengthOfByteStream(AbstractMRMessage m) { 426 return m.getNumDataElements(); // All are same length as message 427 } 428 429 private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class); 430 431}