001package jmri.jmrix.oaktree.simulator; 002 003import java.io.DataInputStream; 004import java.io.DataOutputStream; 005import java.io.IOException; 006import java.io.PipedInputStream; 007import java.io.PipedOutputStream; 008import jmri.jmrix.oaktree.SerialMessage; 009import jmri.jmrix.oaktree.SerialPortController; // no special xSimulatorController 010import jmri.jmrix.oaktree.SerialReply; 011import jmri.jmrix.oaktree.OakTreeSystemConnectionMemo; 012import jmri.util.ImmediatePipedOutputStream; 013import org.slf4j.Logger; 014import org.slf4j.LoggerFactory; 015 016/** 017 * Provide access to a simulated OakTree system. 018 * <p> 019 * Currently, the OakTree SimulatorAdapter reacts to the following commands sent from the user 020 * interface with an appropriate reply {@link #generateReply(SerialMessage)}: 021 * <ul> 022 * <li>Poll (length = 1, reply length = 2) 023 * </ul> 024 * 025 * Based on jmri.jmrix.oaktree.simulator.SimulatorAdapter 2018 026 * <p> 027 * NOTE: Some material in this file was modified from other portions of the 028 * support infrastructure. 029 * 030 * @author Paul Bender, Copyright (C) 2009-2010 031 * @author Mark Underwood, Copyright (C) 2015 032 * @author Egbert Broerse, Copyright (C) 2018 033 */ 034public class SimulatorAdapter extends SerialPortController implements Runnable { 035 036 // private control members 037 private Thread sourceThread; 038 039 private boolean outputBufferEmpty = true; 040 private boolean checkBuffer = true; 041 042 /** 043 * Create a new SimulatorAdapter. 044 */ 045 public SimulatorAdapter() { 046 super(new OakTreeSystemConnectionMemo("O", Bundle.getMessage("OakTreeSimulatorName"))); // pass customized user name 047 setManufacturer(jmri.jmrix.oaktree.SerialConnectionTypeList.OAK); 048 } 049 050 /** 051 * {@inheritDoc} 052 * Simulated input/output pipes. 053 */ 054 @Override 055 public String openPort(String portName, String appName) { 056 try { 057 PipedOutputStream tempPipeI = new ImmediatePipedOutputStream(); 058 log.debug("tempPipeI created"); 059 pout = new DataOutputStream(tempPipeI); 060 inpipe = new DataInputStream(new PipedInputStream(tempPipeI)); 061 log.debug("inpipe created {}", inpipe != null); 062 PipedOutputStream tempPipeO = new ImmediatePipedOutputStream(); 063 outpipe = new DataOutputStream(tempPipeO); 064 pin = new DataInputStream(new PipedInputStream(tempPipeO)); 065 } catch (java.io.IOException e) { 066 log.error("init (pipe): Exception: {}", e.toString()); 067 } 068 opened = true; 069 return null; // indicates OK return 070 } 071 072 /** 073 * Set if the output buffer is empty or full. This should only be set to 074 * false by external processes. 075 * 076 * @param s true if output buffer is empty; false otherwise 077 */ 078 synchronized public void setOutputBufferEmpty(boolean s) { 079 outputBufferEmpty = s; 080 } 081 082 /** 083 * Can the port accept additional characters? The state of CTS determines 084 * this, as there seems to be no way to check the number of queued bytes and 085 * buffer length. This might go false for short intervals, but it might also 086 * stick off if something goes wrong. 087 * 088 * @return true if port can accept additional characters; false otherwise 089 */ 090 public boolean okToSend() { 091 if (checkBuffer) { 092 log.debug("Buffer Empty: {}", outputBufferEmpty); 093 return (outputBufferEmpty); 094 } else { 095 log.debug("No Flow Control or Buffer Check"); 096 return (true); 097 } 098 } 099 100 /** 101 * Set up all of the other objects to operate with an OakTree 102 * connected to this port. 103 */ 104 @Override 105 public void configure() { 106 // connect to the traffic controller 107 log.debug("set tc for memo {}", getSystemConnectionMemo().getUserName()); 108 ((OakTreeSystemConnectionMemo) getSystemConnectionMemo()).getTrafficController().connectPort(this); 109 // do the common manager config 110 ((OakTreeSystemConnectionMemo) getSystemConnectionMemo()).configureManagers(); 111 112 // start the simulator 113 sourceThread = new Thread(this); 114 sourceThread.setName("OakTree Simulator"); 115 sourceThread.setPriority(Thread.MIN_PRIORITY); 116 sourceThread.start(); 117 } 118 119 /** 120 * {@inheritDoc} 121 */ 122 @Override 123 public void connect() throws java.io.IOException { 124 log.debug("connect called"); 125 super.connect(); 126 } 127 128 // Base class methods for the OakTree SerialPortController simulated interface 129 130 /** 131 * {@inheritDoc} 132 */ 133 @Override 134 public DataInputStream getInputStream() { 135 if (!opened || pin == null) { 136 log.error("getInputStream called before load(), stream not available"); 137 } 138 log.debug("DataInputStream pin returned"); 139 return pin; 140 } 141 142 /** 143 * {@inheritDoc} 144 */ 145 @Override 146 public DataOutputStream getOutputStream() { 147 if (!opened || pout == null) { 148 log.error("getOutputStream called before load(), stream not available"); 149 } 150 log.debug("DataOutputStream pout returned"); 151 return pout; 152 } 153 154 /** 155 * {@inheritDoc} 156 * @return always true, given this SimulatorAdapter is running 157 */ 158 @Override 159 public boolean status() { 160 return opened; 161 } 162 163 /** 164 * {@inheritDoc} 165 * 166 * @return null 167 */ 168 @Override 169 public String[] validBaudRates() { 170 log.debug("validBaudRates should not have been invoked"); 171 return new String[]{}; 172 } 173 174 /** 175 * {@inheritDoc} 176 */ 177 @Override 178 public int[] validBaudNumbers() { 179 return new int[]{}; 180 } 181 182 @Override 183 public String getCurrentBaudRate() { 184 return ""; 185 } 186 187 @Override 188 public String getCurrentPortName(){ 189 return ""; 190 } 191 192 @Override 193 public void run() { // start a new thread 194 // This thread has one task. It repeatedly reads from the input pipe 195 // and writes an appropriate response to the output pipe. This is the heart 196 // of the OakTree command station simulation. 197 log.info("OakTree Simulator Started"); 198 while (true) { 199 try { 200 synchronized (this) { 201 wait(50); 202 } 203 } catch (InterruptedException e) { 204 log.debug("interrupted, ending"); 205 return; 206 } 207 SerialMessage m = readMessage(); 208 SerialReply r; 209 if (log.isTraceEnabled()) { 210 StringBuilder buf = new StringBuilder(); 211 if (m != null) { 212 for (int i = 0; i < m.getNumDataElements(); i++) { 213 buf.append(Integer.toHexString(0xFF & m.getElement(i))).append(" "); 214 } 215 } else { 216 buf.append("null message buffer"); 217 } 218 log.trace("OakTree Simulator Thread received message: {}", buf ); // generates a lot of traffic 219 } 220 if (m != null) { 221 r = generateReply(m); 222 if (r != null) { // ignore errors and null replies 223 writeReply(r); 224 if (log.isDebugEnabled()) { 225 StringBuilder buf = new StringBuilder(); 226 for (int i = 0; i < r.getNumDataElements(); i++) { 227 buf.append(Integer.toHexString(0xFF & r.getElement(i))).append(" "); 228 } 229 log.debug("OakTree Simulator Thread sent reply: {}", buf ); 230 } 231 } 232 } 233 } 234 } 235 236 /** 237 * Read one incoming message from the buffer 238 * and set outputBufferEmpty to true. 239 */ 240 private SerialMessage readMessage() { 241 SerialMessage msg = null; 242 // log.debug("Simulator reading message"); // lots of traffic in loop 243 try { 244 if (inpipe != null && inpipe.available() > 0) { 245 msg = loadChars(); 246 } 247 } catch (java.io.IOException e) { 248 // should do something meaningful here. 249 } 250 setOutputBufferEmpty(true); 251 return (msg); 252 } 253 254 /** 255 * This is the heart of the simulation. It translates an 256 * incoming SerialMessage into an outgoing SerialReply. 257 * See {@link jmri.jmrix.oaktree.SerialNode#markChanges(SerialReply)} and 258 * the (draft) OakTree <a href="../package-summary.html">Binary Message Format Summary</a>. 259 * 260 * @param msg the message received in the simulated node 261 * @return a single AokTree message to confirm the requested operation, or a series 262 * of messages for each (fictitious) node/pin/state. To ignore certain commands, return null. 263 */ 264 private SerialReply generateReply(SerialMessage msg) { 265 int nodeaddr = msg.getAddr(); 266 log.debug("Generate Reply to message for node {} (string = {})", nodeaddr, msg.toString()); 267 SerialReply reply = new SerialReply(); // reply length is determined by highest byte added 268 switch (msg.getElement(1)) { 269 case 48: // OakTree poll message 270 reply.setElement(0, nodeaddr); 271 reply.setElement(1, 0x50); 272 if (((OakTreeSystemConnectionMemo) getSystemConnectionMemo()).getTrafficController().getNode(nodeaddr) == null) { 273 log.debug("OakTree Sim generateReply getNode({}) = null", nodeaddr); 274 } else { 275 if (((OakTreeSystemConnectionMemo) getSystemConnectionMemo()).getTrafficController().getNode(nodeaddr).getSensorsActive()) { // input (sensors) status reply 276 log.debug("OakTree Sim generateReply for node {}", nodeaddr); 277 int payload = 0b0001; // dummy stand in for sensor status report; should we fetch known state from jmri node? 278 for (int j = 1; j < 3; j++) { 279 payload |= j << 4; 280 reply.setElement(j + 1, payload); // there could be > 5 elements TODO see SerialNode#markChanges 281 } 282 } else { 283 return null; // prevent NPE 284 } 285 } 286 log.debug("Status Reply generated {}", reply.toString()); 287 return reply; 288 default: 289 log.debug("Message ignored"); 290 return null; 291 } 292 } 293 294 /** 295 * Write reply to output. 296 * <p> 297 * Adapted from jmri.jmrix.nce.simulator.SimulatorAdapter. 298 * 299 * @param r reply on message 300 */ 301 private void writeReply(SerialReply r) { 302 if (r == null) { 303 return; // there is no reply to be sent 304 } 305 for (int i = 0; i < r.getNumDataElements(); i++) { 306 try { 307 outpipe.writeByte((byte) r.getElement(i)); 308 } catch (java.io.IOException ex) { 309 } 310 } 311 try { 312 outpipe.flush(); 313 } catch (java.io.IOException ex) { 314 } 315 } 316 317 /** 318 * Get characters from the input source. 319 * Length is always 5 bytes. 320 * <p> 321 * Only used in the Receive thread. 322 * 323 * @return filled message, only when the message is complete. 324 * @throws IOException when presented by the input source. 325 */ 326 private SerialMessage loadChars() throws java.io.IOException { 327 int i = 1; 328 int char0; 329 byte nextByte; 330 SerialMessage msg = new SerialMessage(5); 331 332 // get 1st byte 333 try { 334 byte byte0 = readByteProtected(inpipe); 335 char0 = (byte0 & 0xFF); 336 log.debug("loadChars read {}", char0); 337 msg.setElement(0, char0); // address 338 } catch (java.io.IOException e) { 339 log.debug("loadChars aborted while reading char 0"); 340 return null; 341 } 342 if (char0 > 0xFF) { 343 // skip as not a node address 344 log.debug("bit not valid as node address"); 345 } 346 347 // read in remaining packets 348 for (i = 1; i < 4; i++) { // read next 4 bytes 349 log.debug("reading rest of message in simulator, element {}", i); 350 try { 351 nextByte = readByteProtected(inpipe); 352 msg.setElement(i, nextByte); 353 } catch (java.io.IOException e) { 354 log.debug("loadChars aborted after {} chars", i); 355 break; 356 } 357 log.debug("loadChars read {} (item {})", Integer.toHexString(nextByte & 0xFF), i); 358 } 359 360 log.debug("OakTree message received by simulator"); 361 return msg; 362 } 363 364 /** 365 * Read a single byte, protecting against various timeouts, etc. 366 * <p> 367 * When a port is set to have a receive timeout (via the 368 * enableReceiveTimeout() method), some will return zero bytes or an 369 * EOFException at the end of the timeout. In that case, the read should be 370 * repeated to get the next real character. 371 * <p> 372 * Copied from DCCppSimulatorAdapter, byte[] from XNetSimAdapter 373 */ 374 private byte readByteProtected(DataInputStream istream) throws java.io.IOException { 375 byte[] rcvBuffer = new byte[1]; 376 while (true) { // loop will repeat until character found 377 int nchars; 378 nchars = istream.read(rcvBuffer, 0, 1); 379 if (nchars > 0) { 380 return rcvBuffer[0]; 381 } 382 } 383 } 384 385 // streams to share with user class 386 private DataOutputStream pout = null; // this is provided to classes who want to write to us 387 private DataInputStream pin = null; // this is provided to classes who want data from us 388 // internal ends of the pipes 389 private DataOutputStream outpipe = null; // feed pin 390 private DataInputStream inpipe = null; // feed pout 391 392 private final static Logger log = LoggerFactory.getLogger(SimulatorAdapter.class); 393 394}