001package jmri.jmrix.cmri.serial; 002 003import java.io.DataInputStream; 004import java.util.ArrayList; 005import jmri.jmrix.AbstractMRListener; 006import jmri.jmrix.AbstractMRMessage; 007import jmri.jmrix.AbstractMRNodeTrafficController; 008import jmri.jmrix.AbstractMRReply; 009import jmri.jmrix.cmri.serial.cmrinetmetrics.CMRInetMetricsData; 010import jmri.jmrix.cmri.serial.cmrinetmetrics.CMRInetMetricsCollector; 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014/** 015 * Converts Stream-based I/O to/from C/MRI serial messages. 016 * <p> 017 * The "SerialInterface" side sends/receives message objects. 018 * <p> 019 * The connection to a SerialPortController is via a pair of *Streams, which 020 * then carry sequences of characters for transmission. Note that this 021 * processing is handled in an independent thread. 022 * <p> 023 * This handles the state transitions, based on the necessary state in each 024 * message. 025 * <p> 026 * Handles initialization, polling, output, and input for multiple Serial Nodes. 027 * 028 * @author Bob Jacobsen Copyright (C) 2003 029 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004 030 * @author Chuck Catania Copyright (C) 2014,2016 CMRInet extensions 031 */ 032public class SerialTrafficController extends AbstractMRNodeTrafficController implements SerialInterface { 033 034 /** 035 * Create a new C/MRI SerialTrafficController instance. 036 */ 037 CMRInetMetricsCollector metricsCollector; 038 039 public SerialTrafficController() { 040 super(); 041 042 // set node range 043 super.init(0, 127); 044 045 // entirely poll driven, so reduce interval 046 mWaitBeforePoll = 5; // default = 25 047 048 metricsCollector = new CMRInetMetricsCollector(); 049 addSerialListener(metricsCollector); 050 } 051 052 // The methods to implement the SerialInterface 053 @Override 054 public synchronized void addSerialListener(SerialListener l) { 055 this.addListener(l); 056 } 057 058 @Override 059 public synchronized void removeSerialListener(SerialListener l) { 060 this.removeListener(l); 061 } 062 063 /** 064 * Initialize a CMRI node. 065 * 066 * @param node the node to initialize 067 */ 068 public void initializeSerialNode(SerialNode node) { 069 synchronized (this) { 070 // find the node in the registered node list 071 for (int i = 0; i < getNumNodes(); i++) { 072 if (getNode(i) == node) { 073 // found node - set up for initialization 074 setMustInit(i, true); 075 return; 076 } 077 } 078 } 079 } 080 081 @Override 082 protected AbstractMRMessage enterProgMode() { 083 log.warn("enterProgMode doesn't make sense for C/MRI serial"); 084 return null; 085 } 086 087 /** 088 * Expose metrics data 089 * @return metrics data 090 */ 091 public CMRInetMetricsData getMetricsData() 092 { 093 return metricsCollector.getMetricData(); 094 } 095 096 @Override 097 protected AbstractMRMessage enterNormalMode() { 098 // can happen during error recovery, null is OK 099 return null; 100 } 101 102 /** 103 * Forward a message to all registered listeners. 104 * 105 * @param client the listener to receive the message; may throw an uncaught 106 * exception if not a 107 * {@link jmri.jmrix.cmri.serial.SerialListener} 108 * @param m the message to forward; may throw an uncaught exception if 109 * not a {@link jmri.jmrix.cmri.serial.SerialMessage} 110 */ 111 @Override 112 protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) { 113 ((SerialListener) client).message((SerialMessage) m); 114// log.info("forward Message"); 115 116 } 117 118 /** 119 * Forward a reply to all registered listeners. 120 * 121 * @param client the listener to receive the reply; may throw an uncaught 122 * exception if not a 123 * {@link jmri.jmrix.cmri.serial.SerialListener} 124 * @param m the reply to forward; may throw an uncaught exception if 125 * not a {@link jmri.jmrix.cmri.serial.SerialMessage} 126 */ 127 @Override 128 protected void forwardReply(AbstractMRListener client, AbstractMRReply m) { 129 ((SerialListener) client).reply((SerialReply) m); 130// log.info("reply Message"); 131 } 132 133 SerialSensorManager mSensorManager = null; 134 135 public void setSensorManager(SerialSensorManager m) { 136 mSensorManager = m; 137 } 138 139 public boolean pollNetwork = true; // true if network polling enabled 140 141 // For later enhancements to network manager 142 //------------------------------------------ 143 private int initTimeout = 500; 144 private int xmitTimeout = 2; 145 // cpNode poll list 146 public ArrayList<Integer> cmriNetPollList = new ArrayList<>(); 147 148 public void setPollNetwork(boolean OnOff) { 149 pollNetwork = OnOff; 150 } 151 152 public boolean getPollNetwork() { 153 return pollNetwork; 154 } 155 156 public void setInitTimeout(int init_Timeout) { 157 initTimeout = init_Timeout; 158 } 159 160 public int getInitTimeout() { 161 return initTimeout; 162 } 163 164 public void setXmitTimeout(int init_XmitTimeout) { 165 xmitTimeout = init_XmitTimeout; 166 } 167 168 public int getXmitTimeout() { 169 return xmitTimeout; 170 } 171 172 /** 173 * Handles initialization, output and polling for C/MRI Serial Nodes from 174 * within the running thread. 175 * <p> 176 * {@inheritDoc} 177 */ 178 @Override 179 protected synchronized AbstractMRMessage pollMessage() { 180 log.trace("pollMessage starts"); 181 // ensure validity of call 182 if (getNumNodes() <= 0) { 183 log.trace("pollMessage ends with zero nodes"); 184 return null; 185 } 186 187 // If total network polling not enabled, exit 188 if (!getPollNetwork()) { 189 log.trace("pollMessage ends with getPollNetwork disabled"); 190 return null; 191 } 192 193 int previousPollPointer = curSerialNodeIndex; 194 updatePollPointer(); // service next node next 195 196 // ensure that each node is initialized 197 SerialNode n = (SerialNode) getNode(curSerialNodeIndex); 198 199 if (getMustInit(curSerialNodeIndex)) { 200 setMustInit(curSerialNodeIndex, false); 201 AbstractMRMessage m = getNode(curSerialNodeIndex).createInitPacket(); 202 log.debug("send init message: {}", m); 203 m.setTimeout(500); // wait for init to finish (milliseconds) 204 // m.setTimeout( getInitTimeout() ); //c2 205 n.setPollStatus(SerialNode.POLLSTATUS_INIT); //c2 206 207 log.trace("pollMessage provides Init message"); 208 return m; 209 } 210 // send Output packet if needed 211 if (getNode(curSerialNodeIndex).mustSend()) { 212 log.debug("request write command to send"); 213 getNode(curSerialNodeIndex).resetMustSend(); 214 AbstractMRMessage m = getNode(curSerialNodeIndex).createOutPacket(); 215 m.setTimeout(2); // no need to wait for output to answer 216 // m.setTimeout( getXmitTimeout() ); // no need to wait for output to answer 217 218 // reset poll pointer update, so next increment will poll from here 219 curSerialNodeIndex = previousPollPointer; 220 log.trace("pollMessage provides Transmit message"); 221 return m; 222 } 223 224 // poll for Sensor input 225 //------------------------------------- 226 // Poll node if polling enabled for this node //c2 227 // update polling status for the node 228 //------------------------------------- 229 230 if (!n.getPollingEnabled()) { 231 n.setPollStatus(SerialNode.POLLSTATUS_IDLE); 232 log.trace("pollMessage ends with getPollingEnabled disabled"); 233 return null; 234 } else if (getNode(curSerialNodeIndex).getSensorsActive()) { 235 if (n.getPollStatus() != SerialNode.POLLSTATUS_POLLING) { 236 n.setPollStatus(SerialNode.POLLSTATUS_POLLING); 237 } 238 239 // Some sensors are active for this node, issue poll 240 SerialMessage m = SerialMessage.getPoll( 241 getNode(curSerialNodeIndex).getNodeAddress()); 242 log.trace("pollMessage ends with poll message"); 243 return m; 244 } else { 245 // no Sensors (inputs) are active for this node 246 log.trace("pollMessage ends with no sensors active"); 247 return null; 248 } 249 } 250 251 /** 252 * Update the curSerialNodeIndex so next node polled next time 253 */ 254 private void updatePollPointer() { 255 curSerialNodeIndex++; 256 if (curSerialNodeIndex >= getNumNodes()) { 257 curSerialNodeIndex = 0; 258 } 259 } 260 261 @Override 262 /** 263 * Log an error message for a message received in an unexpected state. 264 * 265 * The severity depends on whether this is a network connection or not. 266 * 267 * @param State message state. 268 * @param msgString message string. 269 */ 270 protected void unexpectedReplyStateError(int State, String msgString) { 271 String[] packages = this.getClass().getName().split("\\."); 272 String name = (packages.length>=2 ? packages[packages.length-2]+"." :"") 273 +(packages.length>=1 ? packages[packages.length-1] :""); 274 // determine the connection type 275 if (controller instanceof jmri.jmrix.cmri.serial.networkdriver.NetworkDriverAdapter) { 276 // these are probably normal 277 log.debug("reply complete in unexpected state: {} was {} in class {}", State, msgString, name); 278 } else { 279 // other types of connections, make visible 280 log.warn("reply complete in unexpected state: {} was {} in class {}", State, msgString, name); 281 } 282 283 284 } 285 286 @Override 287 protected synchronized void handleTimeout(AbstractMRMessage m, AbstractMRListener l) { 288 // don't use super behavior, as timeout to init, transmit message is normal 289 SerialNode n = (SerialNode) getNode(curSerialNodeIndex); 290 291 // inform node, and if it resets then reinitialize 292 if (getNode(curSerialNodeIndex).handleTimeout(m, l)) { 293 if (n.getPollingEnabled()) //c2 294 { 295 n.setPollStatus(SerialNode.POLLSTATUS_TIMEOUT); 296 metricsCollector.getMetricData().incMetricErrValue(CMRInetMetricsData.CMRInetMetricTimeout); 297 } 298 setMustInit(curSerialNodeIndex, true); 299 } 300 301 } 302 303 @Override 304 protected synchronized void resetTimeout(AbstractMRMessage m) { 305 // don't use super behavior, as timeout to init, transmit message is normal 306 307 // and inform node 308 getNode(curSerialNodeIndex).resetTimeout(m); 309 310 } 311 312 @Override 313 protected AbstractMRListener pollReplyHandler() { 314 return mSensorManager; 315 } 316 317 /** 318 * Forward a pre-formatted message to the actual interface. 319 * 320 * @param m the message to forward 321 * @param reply the listener for the response to m 322 */ 323 @Override 324 public void sendSerialMessage(SerialMessage m, SerialListener reply) { 325 sendMessage(m, reply); 326 } 327 328 @Override 329 protected AbstractMRReply newReply() { 330 return new SerialReply(); 331 } 332 333 @Override 334 protected boolean endOfMessage(AbstractMRReply msg) { 335 // our version of loadChars doesn't invoke this, so it shouldn't be called 336 log.error("Not using endOfMessage, should not be called"); 337 return false; 338 } 339 340 @Override 341 protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException { 342 int i; 343 for (i = 0; i < msg.maxSize(); i++) { 344 byte char1 = readByteProtected(istream); 345 if (char1 == 0x03) { 346 break; // check before DLE handling 347 } 348 if (char1 == 0x10) { 349 char1 = readByteProtected(istream); 350 } 351 msg.setElement(i, char1 & 0xFF); 352 } 353 } 354 355 @Override 356 protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException { 357 // loop looking for the start character 358 while (readByteProtected(istream) != 0x02) { 359 } 360 } 361 362 /** 363 * Add header to the outgoing byte stream. 364 * 365 * @param msg the output byte stream 366 * @param m the message in msg 367 * @return next location in the stream to fill 368 */ 369 @Override 370 protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) { 371 msg[0] = (byte) 0xFF; 372 msg[1] = (byte) 0xFF; 373 msg[2] = (byte) 0x02; // STX 374 return 3; 375 } 376 377 /** 378 * Add trailer to the outgoing byte stream. 379 * 380 * @param msg the output byte stream 381 * @param offset the first byte not yet used 382 * @param m the message in msg 383 */ 384 @Override 385 protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) { 386 msg[offset] = 0x03; // etx 387 } 388 389 /** 390 * Determine how much many bytes the entire message will take, including 391 * space for header and trailer 392 * 393 * @param m The message to be sent 394 * @return Number of bytes 395 */ 396 @Override 397 protected int lengthOfByteStream(AbstractMRMessage m) { 398 int len = m.getNumDataElements(); 399 int cr = 4; 400 return len + cr; 401 } 402 403 private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class); 404 405}