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}