001package jmri.jmrix.maple; 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 * Converts Stream-based I/O to/from Maple 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 Serial Nodes. 025 * 026 * @author Bob Jacobsen Copyright (C) 2003, 2008 027 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004 028 * @author Bob Jacobsen, Dave Duchamp, adapt to use for Maple 2008, 2009, 2010 029 * 030 * @since 2.3.7 031 */ 032@SuppressFBWarnings(value = "IS2_INCONSISTENT_SYNC", justification = "multiple variables accessed outside synchronized core, which is quite suspicious, but code seems to interlock properly") 033public class SerialTrafficController extends AbstractMRNodeTrafficController implements SerialInterface { 034 035 /** 036 * Create a new Maple SerialTrafficController instance. 037 */ 038 public SerialTrafficController() { 039 super(); 040 041 // set node range 042 init(0, 127); 043 044 // entirely poll driven, so reduce interval 045 mWaitBeforePoll = 5; // default = 25 046 047 // initialize input and output utility classes 048 mInputBits = new InputBits(this); 049 mOutputBits = new OutputBits(this); 050 } 051 052 // InputBits and OutputBits 053 private InputBits mInputBits = null; 054 private OutputBits mOutputBits = null; 055 056 public InputBits inputBits(){ 057 return mInputBits; 058 } 059 060 public OutputBits outputBits(){ 061 return mOutputBits; 062 } 063 064 // The methods to implement the SerialInterface 065 066 @Override 067 public synchronized void addSerialListener(SerialListener l) { 068 this.addListener(l); 069 } 070 071 @Override 072 public synchronized void removeSerialListener(SerialListener l) { 073 this.removeListener(l); 074 } 075 076 /** 077 * Public method to set up for initialization of a Serial node. 078 * @param node unused. 079 */ 080 public void initializeSerialNode(SerialNode node) { 081 // dummy routine - Maple System devices do not require initialization 082 } 083 084 @Override 085 protected AbstractMRMessage enterProgMode() { 086 log.warn("enterProgMode doesn't make sense for Maple serial"); 087 return null; 088 } 089 090 @Override 091 protected AbstractMRMessage enterNormalMode() { 092 // can happen during error recovery, null is OK 093 return null; 094 } 095 096 /** 097 * Forward a SerialMessage to all registered SerialInterface listeners. 098 */ 099 @Override 100 protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) { 101 ((SerialListener) client).message((SerialMessage) m); 102 } 103 104 /** 105 * Forward a SerialReply to all registered SerialInterface listeners. 106 */ 107 @Override 108 protected void forwardReply(AbstractMRListener client, AbstractMRReply m) { 109 ((SerialListener) client).reply((SerialReply) m); 110 } 111 112 SerialSensorManager mSensorManager = null; 113 114 public void setSensorManager(SerialSensorManager m) { 115 mSensorManager = m; 116 } 117 118 // initialization not needed ever 119 @Override 120 protected boolean getMustInit(int i) { 121 return false; 122 } 123 124 // With the Maple Systems Protocol, output packets are limited to 99 bits. If there are more than 125 // 99 bits configured, multiple output packets must be sent. The following cycle through that 126 // process. 127 private boolean mNeedSend = true; 128 private int mStartBitNumber = 1; 129 // Similarly the poll command can only poll 99 input bits at a time, so more packets may be needed. 130 private boolean mNeedAdditionalPollPacket = false; 131 private int mStartPollAddress = 1; 132 // The Maple poll response does not contain an address, so the following is needed. 133 private int mSavedPollAddress = 1; 134 135 public int getSavedPollAddress() { 136 return mSavedPollAddress; 137 } 138 private int mCurrentNodeIndexInPoll = -1; 139 140 /** 141 * Handle output and polling for Maple Serial Nodes from within the running 142 * thread. 143 */ 144 @Override 145 protected synchronized AbstractMRMessage pollMessage() { 146 // ensure validity of call - are nodes in yet? 147 if (getNumNodes() <= 0) { 148 return null; 149 } 150 if (curSerialNodeIndex >= getNumNodes()) { 151 curSerialNodeIndex = 0; 152 // process input bits 153 mInputBits.makeChanges(); 154 // initialize send of output bits 155 mNeedSend = true; 156 mStartBitNumber = 1; 157 } 158 // send Output packet if needed 159 if (mNeedSend) { 160 int endBitNumber = mStartBitNumber + 98; 161 if (endBitNumber > OutputBits.getNumOutputBits()) { 162 endBitNumber = OutputBits.getNumOutputBits(); 163 mNeedSend = false; 164 } 165 if (endBitNumber == OutputBits.getNumOutputBits()) { 166 mNeedSend = false; 167 } 168 SerialMessage m = mOutputBits.createOutPacket(mStartBitNumber, endBitNumber); 169 mCurrentNodeIndexInPoll = -1; 170 171 // update the starting bit number if additional packets are needed 172 if (mNeedSend) { 173 mStartBitNumber = endBitNumber + 1; 174 } 175 return m; 176 } 177 // poll for Sensor input 178 int count = 99; 179 if (count > (InputBits.getNumInputBits() - mStartPollAddress + 1)) { 180 count = InputBits.getNumInputBits() - mStartPollAddress + 1; 181 } 182 SerialMessage m = SerialMessage.getPoll( 183 getNode(curSerialNodeIndex).getNodeAddress(), mStartPollAddress, count); 184 mSavedPollAddress = mStartPollAddress; 185 mCurrentNodeIndexInPoll = curSerialNodeIndex; 186 187 // check if additional packet is needed 188 if ((mStartPollAddress + count - 1) < InputBits.getNumInputBits()) { 189 mNeedAdditionalPollPacket = true; 190 mStartPollAddress = mStartPollAddress + 99; 191 } else { 192 mNeedAdditionalPollPacket = false; 193 mStartPollAddress = 1; 194 curSerialNodeIndex++; 195 } 196 return m; 197 } 198 199 protected int wrTimeoutCount = 0; 200 201 public int getWrTimeoutCount() { 202 return wrTimeoutCount; 203 } 204 205 public void resetWrTimeoutCount() { 206 wrTimeoutCount = 0; 207 } 208 209 @Override 210 protected void handleTimeout(AbstractMRMessage m, AbstractMRListener l) { 211 if (m.getElement(3) == 'W' && m.getElement(4) == 'C') { 212 wrTimeoutCount++; // should not happen 213 } else if (m.getElement(3) == 'R' && m.getElement(4) == 'C') { 214 if (mNeedAdditionalPollPacket) { 215 // log.warn("Timeout of poll message, node = {} beg addr = {}", curSerialNodeIndex, mSavedPollAddress); 216 getNode(curSerialNodeIndex).handleTimeout(m, l); 217 } else { 218 // log.warn("Timeout of poll message, node = {} beg addr = {}", (curSerialNodeIndex-1), mSavedPollAddress); 219 getNode(curSerialNodeIndex - 1).handleTimeout(m, l); 220 } 221 } else { 222 log.error("Timeout of unknown message - {}", m.toString()); 223 } 224 } 225 226 @Override 227 protected void resetTimeout(AbstractMRMessage m) { 228 if (mCurrentNodeIndexInPoll < 0) { 229 wrTimeoutCount = 0; // should never happen - outputs should not be timed 230 } else { 231 // don't use super behavior, as timeout to init, transmit message is normal 232 // inform node 233 getNode(mCurrentNodeIndexInPoll).resetTimeout(m); 234 } 235 } 236 237 @Override 238 protected AbstractMRListener pollReplyHandler() { 239 return mSensorManager; 240 } 241 242 /** 243 * Forward a preformatted message to the actual interface. 244 */ 245 @Override 246 public void sendSerialMessage(SerialMessage m, SerialListener reply) { 247 sendMessage(m, reply); 248 } 249 250 @Override 251 protected AbstractMRReply newReply() { 252 return new SerialReply(); 253 } 254 255 @Override 256 protected boolean endOfMessage(AbstractMRReply msg) { 257 // our version of loadChars doesn't invoke this, so it shouldn't be called 258 log.error("Not using endOfMessage, should not be called"); 259 return false; 260 } 261 262 @Override 263 public void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException { 264 int i; 265 boolean first = true; 266 for (i = 0; i < msg.maxSize() - 1; i++) { 267 byte char1 = readByteProtected(istream); 268 msg.setElement(i, char1 & 0xFF); 269 if (first) { 270 first = false; 271 log.debug("start message with {}", char1); 272 } 273 if (char1 == 0x03) { // normal message 274 // get checksum bytes and end 275 log.debug("ETX ends message"); 276 char1 = readByteProtected(istream); 277 msg.setElement(i + 1, char1 & 0xFF); 278 char1 = readByteProtected(istream); 279 msg.setElement(i + 2, char1 & 0xFF); 280 break; // end of message 281 } 282 if (char1 == 0x06) { // ACK OK 283 // get station, command and end 284 log.debug("ACK ends message"); 285 char1 = readByteProtected(istream); // byte 2 286 msg.setElement(++i, char1 & 0xFF); 287 char1 = readByteProtected(istream); // byte 3 288 msg.setElement(++i, char1 & 0xFF); 289 char1 = readByteProtected(istream); // byte 4 290 msg.setElement(++i, char1 & 0xFF); 291 char1 = readByteProtected(istream); // byte 5 292 msg.setElement(++i, char1 & 0xFF); 293 break; // end of message 294 } 295 if (char1 == 0x15) { // NAK error 296 // get station, command, error bytes and end 297 log.debug("NAK ends message"); 298 char1 = readByteProtected(istream); // byte 2 299 msg.setElement(++i, char1 & 0xFF); 300 char1 = readByteProtected(istream); // byte 3 301 msg.setElement(++i, char1 & 0xFF); 302 char1 = readByteProtected(istream); // byte 4 303 msg.setElement(++i, char1 & 0xFF); 304 char1 = readByteProtected(istream); // byte 5 305 msg.setElement(++i, char1 & 0xFF); 306 char1 = readByteProtected(istream); // byte 6 307 msg.setElement(++i, char1 & 0xFF); 308 break; // end of message 309 } 310 } 311 } 312 313 @Override 314 protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException { 315 // don't skip anything 316 } 317 318 /** 319 * Add header to the outgoing byte stream. 320 * 321 * @param msg the output byte stream 322 * @param m the message to add the header to 323 * @return next location in the stream to fill 324 */ 325 @Override 326 protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) { 327 return 0; 328 } 329 330 /** 331 * Add trailer to the outgoing byte stream. 332 * 333 * @param msg The output byte stream 334 * @param offset the first byte not yet used 335 */ 336 @Override 337 protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) { 338 } 339 340 /** 341 * Determine how much many bytes the entire message will take, including 342 * space for header and trailer. 343 * 344 * @param m the message to be sent 345 * @return Number of bytes 346 */ 347 @Override 348 protected int lengthOfByteStream(AbstractMRMessage m) { 349 int len = m.getNumDataElements(); 350 return len; 351 } 352 353 private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class); 354 355}