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