001package jmri.jmrix.oaktree; 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 * Convert Stream-based I/O to/from Oak Tree 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 Oaktree SerialTrafficController instance. Simple implementation. 032 * 033 * @param adaptermemo the associated SystemConnectionMemo 034 */ 035 public SerialTrafficController(OakTreeSystemConnectionMemo adaptermemo) { 036 super(); 037 memo = adaptermemo; 038 log.debug("creating a new GrapevineTrafficController 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 // clear the array of SerialNodes 046 } 047 048 // The methods to implement the SerialInterface 049 @Override 050 public synchronized void addSerialListener(SerialListener l) { 051 this.addListener(l); 052 } 053 054 @Override 055 public synchronized void removeSerialListener(SerialListener l) { 056 this.removeListener(l); 057 } 058 059 /** 060 * Set up for initialization of a Serial node. 061 * @param node node to initialize. 062 */ 063 public void initializeSerialNode(SerialNode node) { 064 synchronized (this) { 065 // find the node in the registered node list 066 for (int i = 0; i < getNumNodes(); i++) { 067 if (getNode(i) == node) { 068 // found node - set up for initialization 069 setMustInit(i, true); 070 return; 071 } 072 } 073 } 074 } 075 076 @Override 077 protected AbstractMRMessage enterProgMode() { 078 log.warn("enterProgMode doesn't make sense for Oak Tree serial"); 079 return null; 080 } 081 082 @Override 083 protected AbstractMRMessage enterNormalMode() { 084 return null; 085 } 086 087 /** 088 * Forward a SerialMessage to all registered SerialInterface listeners. 089 */ 090 @Override 091 protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) { 092 ((SerialListener) client).message((SerialMessage) m); 093 } 094 095 /** 096 * Forward a SerialReply to all registered SerialInterface listeners. 097 */ 098 @Override 099 protected void forwardReply(AbstractMRListener client, AbstractMRReply m) { 100 ((SerialListener) client).reply((SerialReply) m); 101 } 102 103 SerialSensorManager mSensorManager = null; 104 105 public void setSensorManager(SerialSensorManager m) { 106 mSensorManager = m; 107 } 108 109 /** 110 * Handles initialization, output and polling for Oak Tree from within the 111 * running thread 112 */ 113 @Override 114 protected synchronized AbstractMRMessage pollMessage() { 115 // ensure validity of call 116 if (getNumNodes() <= 0) { 117 return null; 118 } 119 120 // move to a new node 121 curSerialNodeIndex++; 122 if (curSerialNodeIndex >= getNumNodes()) { 123 curSerialNodeIndex = 0; 124 } 125 // ensure that each node is initialized 126 if (getMustInit(curSerialNodeIndex)) { 127 setMustInit(curSerialNodeIndex, false); 128 AbstractMRMessage m = getNode(curSerialNodeIndex).createInitPacket(); 129 if (m != null) { // Oak Tree boards don't need this yet 130 log.debug("send init message: {}", m.toString()); 131 m.setTimeout(2000); // wait for init to finish (milliseconds) 132 return m; 133 } // else fall through to continue 134 } 135 // send Output packet if needed 136 if (getNode(curSerialNodeIndex).mustSend()) { 137 log.debug("request write command to send"); 138 AbstractMRMessage m = getNode(curSerialNodeIndex).createOutPacket(); 139 getNode(curSerialNodeIndex).resetMustSend(); 140 m.setTimeout(500); 141 return m; 142 } 143 // poll for Sensor input 144 if (getNode(curSerialNodeIndex).getSensorsActive()) { 145 // Some sensors are active for this node, issue poll 146 SerialMessage m = SerialMessage.getPoll( 147 getNode(curSerialNodeIndex).getNodeAddress()); 148 if (curSerialNodeIndex >= getNumNodes()) { 149 curSerialNodeIndex = 0; 150 } 151 return m; 152 } else { 153 // no Sensors (inputs) are active for this node 154 return null; 155 } 156 } 157 158 @Override 159 protected synchronized 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 protected synchronized void resetTimeout(AbstractMRMessage m) { 172 // inform node 173 getNode(curSerialNodeIndex).resetTimeout(m); 174 175 } 176 177 @Override 178 protected AbstractMRListener pollReplyHandler() { 179 return mSensorManager; 180 } 181 182 /** 183 * Forward a preformatted message to the actual interface. 184 */ 185 @Override 186 public void sendSerialMessage(SerialMessage m, SerialListener reply) { 187 sendMessage(m, reply); 188 } 189 190 /** 191 * Reference to the system connection memo. 192 */ 193 OakTreeSystemConnectionMemo memo = null; 194 195 /** 196 * Get access to the system connection memo associated with this traffic 197 * controller. 198 * 199 * @return associated systemConnectionMemo object 200 */ 201 public OakTreeSystemConnectionMemo getSystemConnectionMemo() { 202 return memo; 203 } 204 205 /** 206 * Set the system connection memo associated with this traffic controller. 207 * 208 * @param m associated systemConnectionMemo object 209 */ 210 public void setSystemConnectionMemo(OakTreeSystemConnectionMemo m) { 211 log.debug("OakTree SerialTrafficController set memo to {}", m.getUserName()); 212 memo = m; 213 } 214 215 @Override 216 protected AbstractMRReply newReply() { 217 return new SerialReply(); 218 } 219 220 @Override 221 protected boolean endOfMessage(AbstractMRReply msg) { 222 // our version of loadChars doesn't invoke this, so it shouldn't be called 223 log.error("Not using endOfMessage, should not be called"); 224 return false; 225 } 226 227 protected int currentAddr = -1; // at startup, can't match 228 protected int incomingLength = 0; 229 230 @Override 231 protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException { 232 // get 1st byte, see if ending too soon 233 byte char1 = readByteProtected(istream); 234 msg.setElement(0, char1 & 0xFF); 235 if ((char1 & 0xFF) != currentAddr) { 236 // mismatch, end early 237 return; 238 } 239 if (incomingLength <= 1) { 240 return; 241 } 242 for (int i = 1; i < incomingLength; i++) { // reading next four bytes 243 char1 = readByteProtected(istream); 244 msg.setElement(i, char1 & 0xFF); 245 } 246 } 247 248 @Override 249 protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException { 250 // does nothing 251 } 252 253 /** 254 * Add header to the outgoing byte stream. 255 * 256 * @param msg The output byte stream 257 * @return next location in the stream to fill 258 */ 259 @Override 260 protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) { 261 return 0; // Do nothing 262 } 263 264 /** 265 * Although this protocol doesn't use a trailer, we implement this method to 266 * set the expected reply length and address for this message. 267 * 268 * @param msg The output byte stream 269 * @param offset the first byte not yet used 270 * @param m the original message 271 */ 272 @Override 273 protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) { 274 incomingLength = ((SerialMessage) m).getResponseLength(); 275 currentAddr = ((SerialMessage) m).getAddr(); 276 return; 277 } 278 279 /** 280 * Determine how much many bytes the entire message will take, including 281 * space for header and trailer. 282 * 283 * @param m The message to be sent 284 * @return Number of bytes 285 */ 286 @Override 287 protected int lengthOfByteStream(AbstractMRMessage m) { 288 return 5; // All are 5 bytes long 289 } 290 291 private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class); 292 293}