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