001package jmri.jmrix.loconet.demoport;
002
003import java.io.*;
004
005import jmri.SystemConnectionMemo;
006import jmri.jmrix.*;
007import jmri.jmrix.loconet.LnConstants;
008import jmri.jmrix.loconet.LocoNetMessage;
009import jmri.util.ThreadingUtil;
010
011/**
012 * Demonstration of replacing the serial port with a fake port.
013 * This class requires a working LocoNet serial port connection.
014 *
015 * @author Daniel Bergqvist (C) 2024
016 */
017public class DemoSerialPort extends AbstractSerialPortController {
018
019    private final DemoPanel _panel;
020    private BufferedOutputStream _outputStream;
021
022    DemoSerialPort(DemoPanel panel, SystemConnectionMemo memo) {
023        super(memo);
024        this._panel = panel;
025    }
026
027    @Override
028    public void configure() {
029        // Do nothing
030    }
031
032    @Override
033    public String openPort(String portName, String appName) {
034        // get and open the primary port
035        currentSerialPort = activatePort(portName, log);
036        if (currentSerialPort == null) {
037            log.error("failed to connect to {}", portName);
038            return Bundle.getMessage("SerialPortNotFound", portName);
039        }
040        log.info("Connecting via {} {}", portName, currentSerialPort);
041
042        setBaudRate(currentSerialPort, 57600);
043        configureLeads(currentSerialPort, true, true);
044        setFlowControl(currentSerialPort, FlowControl.RTSCTS);
045
046        setComPortTimeouts(currentSerialPort, Blocking.READ_SEMI_BLOCKING, 100);
047
048        // report status
049        reportPortStatus(log, portName);
050
051        opened = true;
052
053        return null; // indicates OK return
054    }
055
056    public void startDemo() {
057        log.error("startDemo()");
058        AbstractSerialPortController pc = _panel.getPortController();
059        if (pc == null) {
060            log.error("startDemo(). PortController is null");
061            return;
062        }
063        String portName = pc.getCurrentPortName();
064        log.error("Serial port: {}", pc.getPortSettingsString());
065        pc.replacePortWithFakePort();
066        String result = openPort(portName, "JMRI app");
067        if (result == null) {
068            _panel.addMessage("Connection successful\n");
069            ThreadingUtil.newThread(new LocoNetListener(getInputStream()),
070                    "Demo serial port")
071                    .start();
072            _outputStream = new BufferedOutputStream(getOutputStream());
073        } else {
074            _panel.addMessage(result);
075        }
076    }
077
078    public void throwTurnout(int turnout, boolean throwTurnout) {
079        try {
080            LocoNetMessage msg = new LocoNetMessage(4);
081            msg.setOpCode(LnConstants.OPC_SW_REQ);
082            msg.setElement(1, turnout-1);
083            msg.setElement(2, throwTurnout ? 0x10 : 0x30);
084            msg.setParity();
085            for (int i=0; i < msg.getNumDataElements(); i++) {
086                _outputStream.write(msg.getElement(i));
087            }
088            _outputStream.flush();
089        } catch (IOException ex) {
090            log.error("Exception: {}", ex.getMessage());
091        }
092    }
093
094
095    private final class LocoNetListener implements Runnable {
096
097        private final InputStream _stream;
098        private final int[] data = new int[256];
099        private int pos = 0;
100
101        private LocoNetListener(InputStream stream) {
102            this._stream = stream;
103        }
104
105        private int numBytesInMessage() {
106            switch (data[0] & 0xE0) {
107                case 0x80: return 2;
108                case 0xA0: return 4;
109                case 0xC0: return 6;
110                case 0xE0:
111                    if (pos >= 2) return data[1];
112                    else return 255; // We have only first byte so we don't know length yet.
113                default:
114                    throw new IllegalArgumentException("Unknown length of package");
115            }
116        }
117
118        @Override
119        public void run() {
120            while (! Thread.interrupted() ) {   // loop until asked to stop
121                try {
122                    int b = _stream.read();
123                    _panel.addMessage(String.format("%02X ", b));
124                    if (b < 128 && pos == 0) {
125                        // We are in the middle of a message and have missed
126                        // the beginning of the message. Ignore it.
127                        continue;
128                    }
129                    data[pos++] = b;
130                    if (pos >= numBytesInMessage()) {
131                        LocoNetMessage msg = new LocoNetMessage(data);
132                        _panel.addMessage(msg.toMonitorString());
133                        pos = 0;
134                    }
135                } catch (InterruptedIOException ex) {
136                    // Do nothing, just ignore the error
137                } catch (IOException ex) {
138                    log.error("Exception: {}", ex.getMessage());
139                    return;
140                }
141            }
142        }
143    }
144
145    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DemoSerialPort.class);
146}