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