001package jmri.jmrix.dcc4pc.serialdriver;
002
003import java.io.DataInputStream;
004import java.io.DataOutputStream;
005import java.io.IOException;
006import java.io.InputStream;
007import java.util.ArrayList;
008import java.util.List;
009
010import jmri.SystemConnectionMemo;
011import jmri.jmrix.dcc4pc.Dcc4PcConnectionTypeList;
012import jmri.jmrix.dcc4pc.Dcc4PcPortController;
013import jmri.jmrix.dcc4pc.Dcc4PcSystemConnectionMemo;
014import jmri.jmrix.dcc4pc.Dcc4PcTrafficController;
015
016import org.slf4j.Logger;
017import org.slf4j.LoggerFactory;
018
019import jmri.jmrix.purejavacomm.CommPortIdentifier;
020import jmri.jmrix.purejavacomm.NoSuchPortException;
021import jmri.jmrix.purejavacomm.PortInUseException;
022import jmri.jmrix.purejavacomm.UnsupportedCommOperationException;
023
024/**
025 * Implements SerialPortAdapter for the Dcc4Pc system.
026 * <p>
027 * This connects an Dcc4Pc command station via a serial com port.
028 *
029 * @author Kevin Dickerson Copyright (C) 2012
030 */
031public class SerialDriverAdapter extends Dcc4PcPortController {
032
033    public SerialDriverAdapter() {
034        super(new Dcc4PcSystemConnectionMemo());
035        option1Name = "Programmer"; // NOI18N
036        options.put(option1Name, new Option("Programmer : ", validOption1()));
037        setManufacturer(Dcc4PcConnectionTypeList.DCC4PC);
038    }
039
040    jmri.jmrix.purejavacomm.SerialPort activeSerialPort = null;
041
042    @Override
043    public String openPort(String portName, String appName) {
044
045        try {
046            CommPortIdentifier portID = CommPortIdentifier.getPortIdentifier(portName);
047            try {
048                activeSerialPort = portID.open(getSystemConnectionMemo(), 2000);
049            } catch (PortInUseException p) {
050                return handlePortBusy(p, portName, log);
051            }
052            try {
053                activeSerialPort.setSerialPortParams(115200,
054                        jmri.jmrix.purejavacomm.SerialPort.DATABITS_8,
055                        jmri.jmrix.purejavacomm.SerialPort.STOPBITS_1,
056                        jmri.jmrix.purejavacomm.SerialPort.PARITY_NONE);
057                configureLeadsAndFlowControl(activeSerialPort,
058                        jmri.jmrix.purejavacomm.SerialPort.FLOWCONTROL_NONE);
059            } catch (UnsupportedCommOperationException e) {
060                log.error("Cannot set serial parameters on port {}: {}", portName, e.getMessage());
061            }
062            log.debug("Serial timeout was observed as: {} {}", activeSerialPort.getReceiveTimeout(), activeSerialPort.isReceiveTimeoutEnabled());
063
064            serialStream = activeSerialPort.getInputStream();
065            // purge contents, if any
066            purgeStream(serialStream);
067
068            // report status?
069            if (log.isInfoEnabled()) {
070                log.info("{} port opened at {} baud, sees  DTR: {} RTS: {} DSR: {} CTS: {}  CD: {}", portName, activeSerialPort.getBaudRate(), activeSerialPort.isDTR(), activeSerialPort.isRTS(), activeSerialPort.isDSR(), activeSerialPort.isCTS(), activeSerialPort.isCD());
071            }
072
073            opened = true;
074
075        } catch (NoSuchPortException p) {
076            return handlePortNotFound(p, portName, log);
077        } catch (IOException ex) {
078            log.error("Unexpected exception while opening port {}", portName, ex);
079            return "Unexpected error while opening port " + portName + ": " + ex;
080        }
081        return null; // indicates OK return
082    }
083
084    public void setHandshake(int mode) {
085        try {
086            activeSerialPort.setFlowControlMode(mode);
087        } catch (UnsupportedCommOperationException ex) {
088            log.error("Unexpected exception while setting COM port handshake mode", ex);
089        }
090    }
091
092    public jmri.jmrix.purejavacomm.SerialPort getSerialPort() {
093        return activeSerialPort;
094    }
095
096    /**
097     * Close the port after it has failed, so it can be opened again with
098     * {@link #openPort}.
099     */
100    public void closeSerialPort() {
101        opened = false;
102        jmri.jmrix.purejavacomm.SerialPort p = activeSerialPort;
103        if (p != null) {
104            try {
105                p.close();
106            } catch (RuntimeException e) {
107                log.debug("Closing the failed port", e);
108            }
109        }
110    }
111
112    /**
113     * @return true if the operating system lists this connection's port. A
114     *         USB interface's port disappears while it is unplugged.
115     */
116    public boolean isPortPresent() {
117        return getActualPortNames().contains(getCurrentPortName());
118    }
119
120    /**
121     * Option 1 controls the connection used for programming.
122     * @return array with options for programming.
123     */
124    public String[] validOption1() {
125        List<SystemConnectionMemo> connList = jmri.InstanceManager.getList(SystemConnectionMemo.class);
126        if (!connList.isEmpty()) {
127            ArrayList<String> progConn = new ArrayList<>();
128            progConn.add("");
129            String userName = "Dcc4Pc";
130            if (this.getSystemConnectionMemo() != null) {
131                userName = this.getSystemConnectionMemo().getUserName();
132            }
133            for (int i = 0; i < connList.size(); i++) {
134                SystemConnectionMemo scm = connList.get(i);
135                if ((scm.provides(jmri.AddressedProgrammerManager.class) || scm.provides(jmri.GlobalProgrammerManager.class))
136                        && (!scm.getUserName().equals(userName))) {
137                    progConn.add(scm.getUserName());
138                }
139            }
140            return progConn.toArray(new String[progConn.size()]);
141        } else {
142            return new String[]{""};
143        }
144    }
145
146    /**
147     * {@inheritDoc}
148     * <p>
149     * The Programmer choices are worked out each time: the list made when
150     * this connection was created can't include connections that start after
151     * it, and LocoNet often does. The saved choice stays in the list even if
152     * that connection isn't running, so opening Preferences doesn't clear it.
153     */
154    @Override
155    public String[] getOptionChoices(String option) {
156        if (!option1Name.equals(option)) {
157            return super.getOptionChoices(option);
158        }
159        List<String> choices = new ArrayList<>(java.util.Arrays.asList(validOption1()));
160        String current = getOptionState(option1Name);
161        if (current != null && !current.isEmpty() && !choices.contains(current)) {
162            choices.add(current);
163        }
164        return choices.toArray(new String[choices.size()]);
165    }
166
167    /**
168     * Get a String that says what Option 2 represents May be an empty string,
169     * but will not be null
170     * @return option to match detected locos to roster.
171     */
172    public String option2Name() {
173        return "Match Detected Locos to Roster: ";
174    }
175
176    /**
177     * Set the second port option. Only to be used after construction, but
178     * before the openPort call
179     */
180    @Override
181    public void configureOption2(String value) {
182        super.configureOption2(value);
183        log.debug("configureOption2: {}", value);
184        //Not yet implemented
185        /*boolean enable = true;
186         if(value.equals("No"))
187         enable = false;
188         adaptermemo.getRailCommManager().setRosterEntryDiscoveryAllowed(enable);*/
189    }
190
191    // base class methods for the Dcc4PcPortController interface
192    @Override
193    public DataInputStream getInputStream() {
194        if (!opened) {
195            log.error("getInputStream called before load(), stream not available");
196            return null;
197        }
198        return new DataInputStream(serialStream);
199    }
200
201    @Override
202    public DataOutputStream getOutputStream() {
203        if (!opened) {
204            log.error("getOutputStream called before load(), stream not available");
205        }
206        try {
207            return new DataOutputStream(activeSerialPort.getOutputStream());
208        } catch (java.io.IOException e) {
209            log.error("getOutputStream exception", e);
210        }
211        return null;
212    }
213
214    /**
215     * {@inheritDoc}
216     *
217     * Currently only 115,200 bps
218     */
219    @Override
220    public String[] validBaudRates() {
221        return new String[]{"115,200 bps"};
222    }
223
224    /**
225     * {@inheritDoc}
226     */
227    @Override
228    public int[] validBaudNumbers() {
229        return new int[]{115200};
230    }
231
232    @Override
233    public int defaultBaudIndex() {
234        return 0;
235    }
236
237    InputStream serialStream = null;
238
239    /**
240     * set up all of the other objects to operate with an Dcc4Pc command station
241     * connected to this port
242     */
243    @Override
244    public void configure() {
245        // connect to the traffic controller
246        Dcc4PcTrafficController control = new Dcc4PcTrafficController();
247        this.getSystemConnectionMemo().setDcc4PcTrafficController(control);
248        control.connectPort(this);
249        this.getSystemConnectionMemo().configureManagers();
250        // Programming on Main goes out through another connection (by default, the default
251        // command station's); RailCom brings the reply back
252        this.getSystemConnectionMemo().useProgrammerFrom(getOptionState(option1Name));
253
254    }
255
256    private static final Logger log = LoggerFactory.getLogger(SerialDriverAdapter.class);
257
258}