001package jmri.jmrix.direct.simulator; 002 003import java.io.*; 004 005import org.slf4j.Logger; 006import org.slf4j.LoggerFactory; 007 008// no special xSimulatorController 009import jmri.jmrix.direct.*; 010import jmri.util.ImmediatePipedOutputStream; 011 012/** 013 * Provide access to a simulated DirectDrive system. 014 * <p> 015 * Currently, the Direct SimulatorAdapter reacts to the following commands sent from the user 016 * interface with an appropriate reply {@link #generateReply(Message)}: 017 * <ul> 018 * <li>N/A 019 * </ul> 020 * 021 * Based on jmri.jmrix.lenz.xnetsimulator.XNetSimulatorAdapter / EasyDCCSimulatorAdapter 2017 022 * <p> 023 * NOTE: Some material in this file was modified from other portions of the 024 * support infrastructure. 025 * 026 * @author Paul Bender, Copyright (C) 2009-2010 027 * @author Mark Underwood, Copyright (C) 2015 028 * @author Egbert Broerse, Copyright (C) 2018 029 */ 030@SuppressWarnings("javadoc") 031public class SimulatorAdapter extends PortController implements Runnable { 032 033 // private control members 034 private Thread sourceThread; 035 036 private boolean outputBufferEmpty = true; 037 private boolean checkBuffer = true; 038 039 /** 040 * Create a new SimulatorAdapter. 041 */ 042 public SimulatorAdapter() { 043 super(new DirectSystemConnectionMemo("N", Bundle.getMessage("DirectSimulatorName"))); // pass customized user name 044 045 setManufacturer(jmri.jmrix.direct.DirectConnectionTypeList.DIRECT); 046 } 047 048 /** 049 * {@inheritDoc} 050 * Simulated input/output pipes. 051 */ 052 @Override 053 public String openPort(String portName, String appName) { 054 try { 055 PipedOutputStream tempPipeI = new ImmediatePipedOutputStream(); 056 log.debug("tempPipeI created"); 057 pout = new DataOutputStream(tempPipeI); 058 inpipe = new DataInputStream(new PipedInputStream(tempPipeI)); 059 log.debug("inpipe created {}", inpipe != null); 060 PipedOutputStream tempPipeO = new ImmediatePipedOutputStream(); 061 outpipe = new DataOutputStream(tempPipeO); 062 pin = new DataInputStream(new PipedInputStream(tempPipeO)); 063 } catch (java.io.IOException e) { 064 log.error("init (pipe): Exception: {}", e.toString()); 065 } 066 opened = true; 067 return null; // indicates OK return 068 } 069 070 /** 071 * Set if the output buffer is empty or full. This should only be set to 072 * false by external processes. 073 * 074 * @param s true if output buffer is empty; false otherwise 075 */ 076 synchronized public void setOutputBufferEmpty(boolean s) { 077 outputBufferEmpty = s; 078 } 079 080 /** 081 * Can the port accept additional characters? The state of CTS determines 082 * this, as there seems to be no way to check the number of queued bytes and 083 * buffer length. This might go false for short intervals, but it might also 084 * stick off if something goes wrong. 085 * 086 * @return true if port can accept additional characters; false otherwise 087 */ 088 public boolean okToSend() { 089 if (checkBuffer) { 090 log.debug("Buffer Empty: {}", outputBufferEmpty); 091 return (outputBufferEmpty); 092 } else { 093 log.debug("No Flow Control or Buffer Check"); 094 return (true); 095 } 096 } 097 098 /** 099 * Set up all of the other objects to operate with a DirectSimulator 100 * connected to this port. 101 */ 102 @Override 103 public void configure() { 104 // connect to the traffic controller 105 TrafficController tc = new TrafficController((jmri.jmrix.direct.DirectSystemConnectionMemo)getSystemConnectionMemo()); 106 ((jmri.jmrix.direct.DirectSystemConnectionMemo)getSystemConnectionMemo()).setTrafficController(tc); 107 // connect to the traffic controller 108 tc.connectPort(this); 109 log.debug("set tc for memo {}", getSystemConnectionMemo().getUserName()); 110 111 // do the common manager config 112 ((jmri.jmrix.direct.DirectSystemConnectionMemo)getSystemConnectionMemo()).configureManagers(); 113 114 // start the simulator: Notice that normally, transmission is not a threaded operation! 115 sourceThread = new Thread(this); 116 sourceThread.setName("Direct Simulator"); // NOI18N 117 sourceThread.setPriority(Thread.MIN_PRIORITY); 118 sourceThread.start(); 119 } 120 121 /** 122 * {@inheritDoc} 123 */ 124 @Override 125 public void connect() throws java.io.IOException { 126 log.debug("connect called"); 127 super.connect(); 128 } 129 130 // Base class methods for the Direct SerialPortController simulated interface 131 132 /** 133 * {@inheritDoc} 134 */ 135 @Override 136 public DataInputStream getInputStream() { 137 if (!opened || pin == null) { 138 log.error("getInputStream called before load(), stream not available"); 139 } 140 log.debug("DataInputStream pin returned"); 141 return pin; 142 } 143 144 /** 145 * {@inheritDoc} 146 */ 147 @Override 148 public DataOutputStream getOutputStream() { 149 if (!opened || pout == null) { 150 log.error("getOutputStream called before load(), stream not available"); 151 } 152 log.debug("DataOutputStream pout returned"); 153 return pout; 154 } 155 156 /** 157 * {@inheritDoc} 158 * @return always true, given this SimulatorAdapter is running 159 */ 160 @Override 161 public boolean status() { 162 return opened; 163 } 164 165 /** 166 * {@inheritDoc} 167 * 168 * @return null 169 */ 170 @Override 171 public String[] validBaudRates() { 172 log.debug("validBaudRates should not have been invoked"); 173 return new String[]{}; 174 } 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override 180 public int[] validBaudNumbers() { 181 return new int[]{}; 182 } 183 184 @Override 185 public String getCurrentBaudRate() { 186 return ""; 187 } 188 189 @Override 190 public String getCurrentPortName(){ 191 return ""; 192 } 193 194 @Override 195 public void run() { // start a new thread 196 // This thread has one task. It repeatedly reads from the input pipe 197 // and writes an appropriate response to the output pipe. This is the heart 198 // of the Direct command station simulation. 199 log.info("Direct Simulator Started"); 200 while (true) { 201 try { 202 synchronized (this) { 203 wait(50); 204 } 205 } catch (InterruptedException e) { 206 log.debug("interrupted, ending"); 207 return; 208 } 209 Message m = readMessage(); 210 Reply r; 211 if (log.isDebugEnabled()) { 212 StringBuffer buf = new StringBuffer(); 213 if (m != null) { 214 for (int i = 0; i < m.getNumDataElements(); i++) { 215 buf.append(Integer.toHexString(0xFF & m.getElement(i))).append(" "); 216 } 217 } else { 218 buf.append("null message buffer"); 219 } 220 log.trace("Direct Simulator Thread received message: {}", buf); // generates a lot of traffic 221 } 222 if (m != null) { 223 r = generateReply(m); 224 if (r != null) { // ignore errors 225 writeReply(r); 226 if (log.isDebugEnabled()) { 227 StringBuffer buf = new StringBuffer(); 228 for (int i = 0; i < r.getNumDataElements(); i++) { 229 buf.append(Integer.toHexString(0xFF & r.getElement(i))).append(" "); 230 } 231 log.debug("Direct Simulator Thread sent reply: {}", buf); 232 } 233 } 234 } 235 } 236 } 237 238 /** 239 * Read one incoming message from the buffer and set 240 * outputBufferEmpty to true. 241 */ 242 private Message readMessage() { 243 Message msg = null; 244 // log.debug("Simulator reading message"); 245 try { 246 if (inpipe != null && inpipe.available() > 0) { 247 msg = loadChars(); 248 } 249 } catch (java.io.IOException e) { 250 // should do something meaningful here. 251 } 252 setOutputBufferEmpty(true); 253 return (msg); 254 } 255 256 /** 257 * This is the heart of the simulation. It translates an 258 * incoming Message into an outgoing Reply. 259 * 260 * @param msg the message received 261 * @return a single Direct message to confirm the requested operation. 262 * To ignore certain commands, return null. 263 */ 264 private Reply generateReply(Message msg) { 265 log.debug("Generate Reply to message (string = {})", msg.toString()); 266 267 Reply reply = new Reply(); // reply length is determined by highest byte added 268 int addr = msg.getAddr(); // address from element(0) 269 log.debug("Message address={}", addr); 270 271 switch (addr) { // use a more meaningful key 272 273 case 3: 274 reply.setElement(0, addr | 0x80); 275 reply.setElement(1, 0); // pretend speed 0 276 // no parity 277 log.debug("Reply generated {}", reply); 278 break; 279 280 default: 281 reply = null; 282 log.debug("Message ignored"); 283 } 284 return (reply); 285 } 286 287 /** 288 * Write reply to output. 289 * <p> 290 * Adapted from jmri.jmrix.nce.simulator.SimulatorAdapter. 291 * 292 * @param r reply on message 293 */ 294 private void writeReply(Reply r) { 295 if (r == null) { 296 return; // there is no reply to be sent 297 } 298 for (int i = 0; i < r.getNumDataElements(); i++) { 299 try { 300 outpipe.writeByte((byte) r.getElement(i)); 301 } catch (java.io.IOException ex) { 302 } 303 } 304 try { 305 outpipe.flush(); 306 } catch (java.io.IOException ex) { 307 } 308 } 309 310 /** 311 * Get characters from the input source. 312 * <p> 313 * Only used in the Receive thread. 314 * 315 * @return filled message, only when the message is complete. 316 * @throws IOException when presented by the input source. 317 */ 318 private Message loadChars() throws java.io.IOException { 319 int nchars; 320 byte[] rcvBuffer = new byte[32]; 321 322 nchars = inpipe.read(rcvBuffer, 0, 32); 323 log.debug("new message received"); 324 Message msg = new Message(nchars); 325 326 for (int i = 0; i < nchars; i++) { 327 msg.setElement(i, rcvBuffer[i] & 0xFF); 328 } 329 return msg; 330 } 331 332 // streams to share with user class 333 private DataOutputStream pout = null; // this is provided to classes who want to write to us 334 private DataInputStream pin = null; // this is provided to classes who want data from us 335 // internal ends of the pipes 336 private DataOutputStream outpipe = null; // feed pin 337 private DataInputStream inpipe = null; // feed pout 338 339 private final static Logger log = LoggerFactory.getLogger(SimulatorAdapter.class); 340 341}