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}