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}