001package jmri.jmrix.secsi; 002 003import java.io.DataInputStream; 004import jmri.jmrix.AbstractMRListener; 005import jmri.jmrix.AbstractMRMessage; 006import jmri.jmrix.AbstractMRNodeTrafficController; 007import jmri.jmrix.AbstractMRReply; 008import org.slf4j.Logger; 009import org.slf4j.LoggerFactory; 010 011/** 012 * Converts Stream-based I/O to/from SECSI serial messages. 013 * <p> 014 * The "SerialInterface" side sends/receives message objects. 015 * <p> 016 * The connection to a SerialPortController is via a pair of *Streams, which 017 * then carry sequences of characters for transmission. Note that this 018 * processing is handled in an independent thread. 019 * <p> 020 * This handles the state transitions, based on the necessary state in each 021 * message. 022 * <p> 023 * Handles initialization, polling, output, and input for multiple Serial Nodes. 024 * 025 * @author Bob Jacobsen Copyright (C) 2003, 2006 026 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004 027 */ 028public class SerialTrafficController extends AbstractMRNodeTrafficController implements SerialInterface { 029 030 /** 031 * Create a new SerialTrafficController instance. 032 * 033 * @param adaptermemo the associated SystemConnectionMemo 034 */ 035 public SerialTrafficController(SecsiSystemConnectionMemo adaptermemo) { 036 super(); 037 memo = adaptermemo; 038 log.debug("creating a new SECSI SerialTrafficController object on {}", adaptermemo.getSystemPrefix()); 039 // set node range 040 init(0, 255); 041 042 // entirely poll driven, so reduce interval 043 mWaitBeforePoll = 25; // default = 25 044 } 045 046 // The methods to implement the SerialInterface 047 048 @Override 049 public synchronized void addSerialListener(SerialListener l) { 050 this.addListener(l); 051 } 052 053 @Override 054 public synchronized void removeSerialListener(SerialListener l) { 055 this.removeListener(l); 056 } 057 058 /** 059 * Set up for initialization of a Serial node. 060 * @param node node to initialize. 061 */ 062 public void initializeSerialNode(SerialNode node) { 063 synchronized (this) { 064 // find the node in the registered node list 065 for (int i = 0; i < getNumNodes(); i++) { 066 if (getNode(i) == node) { 067 // found node - set up for initialization 068 setMustInit(i, true); 069 return; 070 } 071 } 072 } 073 } 074 075 @Override 076 protected AbstractMRMessage enterProgMode() { 077 log.warn("enterProgMode doesn't make sense for SECSI serial"); 078 return null; 079 } 080 081 @Override 082 protected AbstractMRMessage enterNormalMode() { 083 return null; 084 } 085 086 /** 087 * Forward a SerialMessage to all registered SerialInterface listeners. 088 */ 089 @Override 090 protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) { 091 ((SerialListener) client).message((SerialMessage) m); 092 } 093 094 /** 095 * Forward a SerialReply to all registered SerialInterface listeners. 096 */ 097 @Override 098 protected void forwardReply(AbstractMRListener client, AbstractMRReply m) { 099 ((SerialListener) client).reply((SerialReply) m); 100 } 101 102 SerialSensorManager mSensorManager = null; 103 104 public void setSensorManager(SerialSensorManager m) { 105 mSensorManager = m; 106 } 107 108 /** 109 * Handles initialization, output and polling from within the running thread 110 */ 111 @Override 112 protected synchronized AbstractMRMessage pollMessage() { 113 // ensure validity of call 114 if (getNumNodes() <= 0) { 115 return null; 116 } 117 118 // move to a new node 119 curSerialNodeIndex++; 120 if (curSerialNodeIndex >= getNumNodes()) { 121 curSerialNodeIndex = 0; 122 } 123 // ensure that each node is initialized 124 if (getMustInit(curSerialNodeIndex)) { 125 setMustInit(curSerialNodeIndex, false); 126 AbstractMRMessage m = getNode(curSerialNodeIndex).createInitPacket(); 127 if (m != null) { // SECSI boards don't need this yet, so createInitPacket() returns null 128 log.debug("send init message: {} to node {}", m.toString(), curSerialNodeIndex); 129 m.setTimeout(2000); // wait for init to finish (milliseconds) 130 return m; 131 } // else fall through to continue 132 } 133 // send Output packet if needed 134 if (getNode(curSerialNodeIndex).mustSend()) { 135 log.debug("request write command to send"); 136 AbstractMRMessage m = getNode(curSerialNodeIndex).createOutPacket(); 137 getNode(curSerialNodeIndex).resetMustSend(); 138 m.setTimeout(500); 139 return m; 140 } 141 // poll for Sensor input 142 if (getNode(curSerialNodeIndex).getSensorsActive()) { 143 // Some sensors are active for this node, issue poll 144 log.debug("poll command start for {} nodes", getNumNodes()); 145 SerialMessage m = SerialMessage.getPoll( 146 getNode(curSerialNodeIndex).getNodeAddress()); 147 if (curSerialNodeIndex >= getNumNodes()) { 148 curSerialNodeIndex = 0; 149 } 150 log.debug("poll command created"); 151 return m; 152 } else { 153 // no Sensors (inputs) are active for this node 154 return null; 155 } 156 } 157 158 @Override 159 synchronized protected void handleTimeout(AbstractMRMessage m, AbstractMRListener l) { 160 // inform node, and if it resets then reinitialize 161 if (getNode(curSerialNodeIndex) != null) { 162 if (getNode(curSerialNodeIndex).handleTimeout(m, l)) { 163 setMustInit(curSerialNodeIndex, true); 164 } else { 165 log.warn("Timeout can't be handled due to missing node (index {})", curSerialNodeIndex); 166 } 167 } 168 } 169 170 @Override 171 synchronized protected void resetTimeout(AbstractMRMessage m) { 172 // inform node 173 getNode(curSerialNodeIndex).resetTimeout(m); 174 } 175 176 @Override 177 protected AbstractMRListener pollReplyHandler() { 178 return mSensorManager; 179 } 180 181 /** 182 * Forward a preformatted message to the actual interface. 183 */ 184 @Override 185 public void sendSerialMessage(SerialMessage m, SerialListener reply) { 186 sendMessage(m, reply); 187 } 188 189 /** 190 * Reference to the system connection memo. 191 */ 192 SecsiSystemConnectionMemo memo = null; 193 194 /** 195 * Get access to the system connection memo associated with this traffic 196 * controller. 197 * 198 * @return associated systemConnectionMemo object 199 */ 200 public SecsiSystemConnectionMemo getSystemConnectionMemo() { 201 return memo; 202 } 203 204 /** 205 * Set the system connection memo associated with this traffic controller. 206 * 207 * @param m associated systemConnectionMemo object 208 */ 209 public void setSystemConnectionMemo(SecsiSystemConnectionMemo m) { 210 log.debug("Secsi SerialTrafficController set memo to {}", m.getUserName()); 211 memo = m; 212 } 213 214 @Override 215 protected AbstractMRReply newReply() { 216 return new SerialReply(); 217 } 218 219 @Override 220 protected boolean endOfMessage(AbstractMRReply msg) { 221 // our version of loadChars doesn't invoke this, so it shouldn't be called 222 log.error("Not using endOfMessage, should not be called"); 223 return false; 224 } 225 226 protected int currentAddr = -1; // at startup, can't match 227 protected int incomingLength = 0; 228 229 @Override 230 protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException { 231 // get 1st byte, see if ending too soon 232 byte char1 = readByteProtected(istream); 233 msg.setElement(0, char1 & 0xFF); 234 if ((char1 & 0xFF) != currentAddr) { 235 // mismatch, end early 236 return; 237 } 238 if (incomingLength <= 1) { 239 return; 240 } 241 for (int i = 1; i < incomingLength; i++) { // reading next four bytes 242 char1 = readByteProtected(istream); 243 msg.setElement(i, char1 & 0xFF); 244 } 245 } 246 247 @Override 248 protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException { 249 // does nothing 250 } 251 252 /** 253 * Add header to the outgoing byte stream. 254 * 255 * @param msg The output byte stream 256 * @return next location in the stream to fill 257 */ 258 @Override 259 protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) { 260 return 0; // Do nothing 261 } 262 263 /** 264 * Although this protocol doesn't use a trailer, we implement this method to 265 * set the expected reply length and address for this message. 266 * 267 * @param msg The output byte stream 268 * @param offset the first byte not yet used 269 * @param m the original message 270 */ 271 @Override 272 protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) { 273 incomingLength = ((SerialMessage) m).getResponseLength(); 274 currentAddr = ((SerialMessage) m).getAddr(); 275 return; 276 } 277 278 /** 279 * Determine how many bytes the entire message will take, including 280 * space for header and trailer. 281 * @see SerialMessage#getPoll(int) 282 * @see SerialNode#createOutPacket() 283 * 284 * @param m the message to be sent 285 * @return number of bytes in message 286 */ 287 @Override 288 protected int lengthOfByteStream(AbstractMRMessage m) { 289 return m.getNumDataElements(); // Length varies by type. A fixed size of 5 as 290 // was copied from OakTree.SerialTrafficController#lengthOfByteStream 291 // caused an ArrayIndexOutOfBounds exception in AbstractSerialTrafficController 292 // over the 9 byte Node Reply message 293 } 294 295 private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class); 296 297}