001package jmri.jmrix.powerline.serialdriver;
002
003import java.io.DataInputStream;
004import java.io.DataOutputStream;
005import java.io.IOException;
006import java.io.InputStream;
007import java.util.Arrays;
008import jmri.jmrix.powerline.SerialPortController;
009import jmri.jmrix.powerline.SerialSystemConnectionMemo;
010import jmri.jmrix.powerline.SerialTrafficController;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013import purejavacomm.CommPortIdentifier;
014import purejavacomm.NoSuchPortException;
015import purejavacomm.PortInUseException;
016import purejavacomm.SerialPort;
017import purejavacomm.UnsupportedCommOperationException;
018
019/**
020 * Provide access to Powerline devices via a serial com port.
021 * Derived from the Oaktree code.
022 *
023 * @author Bob Jacobsen Copyright (C) 2006, 2007, 2008
024 * @author Ken Cameron, (C) 2009, sensors from poll replies Converted to multiple connection
025 * @author kcameron Copyright (C) 2011
026 */
027public class SerialDriverAdapter extends SerialPortController {
028
029    SerialPort activeSerialPort = null;
030
031    public SerialDriverAdapter() {
032        super(new SerialSystemConnectionMemo());
033        option1Name = "Adapter"; // NOI18N
034        options.put(option1Name, new Option("Adapter", stdOption1Values));
035        this.manufacturerName = jmri.jmrix.powerline.SerialConnectionTypeList.POWERLINE;
036    }
037
038    @Override
039    public String openPort(String portName, String appName) {
040        try {
041            // get and open the primary port
042            CommPortIdentifier portID = CommPortIdentifier.getPortIdentifier(portName);
043            try {
044                activeSerialPort = (SerialPort) portID.open(appName, 2000); // name of program, msec to wait
045            } catch (PortInUseException p) {
046                return handlePortBusy(p, portName, log);
047            }
048            // try to set it for serial
049            try {
050                setSerialPort();
051            } catch (UnsupportedCommOperationException e) {
052                log.error("Cannot set serial parameters on port {}: {}", portName, e.getMessage());
053                return "Cannot set serial parameters on port " + portName + ": " + e.getMessage();
054            }
055
056            // set framing (end) character
057            try {
058                log.debug("Serial framing was observed as: {} {}", activeSerialPort.isReceiveFramingEnabled(),
059                        activeSerialPort.getReceiveFramingByte());
060            } catch (Exception ef) {
061                log.debug("failed to set serial framing: ", ef);
062            }
063
064            // set timeout; framing should work before this anyway
065            try {
066                activeSerialPort.enableReceiveTimeout(10);
067                log.debug("Serial timeout was observed as: {} {}", activeSerialPort.getReceiveTimeout(), activeSerialPort.isReceiveTimeoutEnabled());
068            } catch (Exception et) {
069                log.info("failed to set serial timeout: ", et);
070            }
071
072            // get and save stream
073            serialStream = activeSerialPort.getInputStream();
074
075            // purge contents, if any
076            purgeStream(serialStream);
077
078            // report status?
079            if (log.isInfoEnabled()) {
080                // report now
081                log.info("{} port opened at {} baud with DTR: {} RTS: {} DSR: {} CTS: {}  CD: {}", portName, activeSerialPort.getBaudRate(), activeSerialPort.isDTR(), activeSerialPort.isRTS(), activeSerialPort.isDSR(), activeSerialPort.isCTS(), activeSerialPort.isCD());
082            }
083            if (log.isDebugEnabled()) {
084                // report additional status
085                log.debug(" port flow control shows {}", // NOI18N
086                        (activeSerialPort.getFlowControlMode() == SerialPort.FLOWCONTROL_RTSCTS_OUT ? "hardware flow control" : "no flow control")); // NOI18N
087
088                // log events
089                setPortEventLogging(activeSerialPort);
090            }
091
092            opened = true;
093
094        } catch (NoSuchPortException p) {
095            return handlePortNotFound(p, portName, log);
096        } catch (IOException ex) {
097            log.error("Unexpected exception while opening port {} ", portName, ex);
098            return "Unexpected error while opening port " + portName + ": " + ex;
099        }
100
101        return null; // normal operation
102    }
103
104    /**
105     * Can the port accept additional characters? Yes, always
106     * @return boolean of xmit port status
107     */
108    public boolean okToSend() {
109        return true;
110    }
111
112    /**
113     * Set up all of the other objects to operate connected to this port.
114     */
115    @Override
116    public void configure() {
117        SerialTrafficController tc = null;
118        // set up the system connection first
119        String opt1 = getOptionState(option1Name);
120        if (opt1.equals("CM11")) {
121            // create a CM11 port controller
122            this.setSystemConnectionMemo(new jmri.jmrix.powerline.cm11.SpecificSystemConnectionMemo());
123            tc = new jmri.jmrix.powerline.cm11.SpecificTrafficController(this.getSystemConnectionMemo());
124        } else if (opt1.equals("CP290")) {
125            // create a CP290 port controller
126            this.setSystemConnectionMemo(new jmri.jmrix.powerline.cp290.SpecificSystemConnectionMemo());
127            tc = new jmri.jmrix.powerline.cp290.SpecificTrafficController(this.getSystemConnectionMemo());
128        } else if (opt1.equals("Insteon 2412S")) {
129            // create an Insteon 2412s port controller
130            this.setSystemConnectionMemo(new jmri.jmrix.powerline.insteon2412s.SpecificSystemConnectionMemo());
131            tc = new jmri.jmrix.powerline.insteon2412s.SpecificTrafficController(this.getSystemConnectionMemo());
132        } else {
133            // no connection at all - warn
134            log.warn("protocol option {} defaults to CM11", opt1);
135            // create a CM11 port controller
136            this.setSystemConnectionMemo(new jmri.jmrix.powerline.cm11.SpecificSystemConnectionMemo());
137            tc = new jmri.jmrix.powerline.cm11.SpecificTrafficController(this.getSystemConnectionMemo());
138        }
139
140        // connect to the traffic controller
141        this.getSystemConnectionMemo().setTrafficController(tc);
142        tc.setAdapterMemo(this.getSystemConnectionMemo());
143        this.getSystemConnectionMemo().configureManagers();
144        tc.connectPort(this);
145        // Configure the form of serial address validation for this connection
146        this.getSystemConnectionMemo().setSerialAddress(new jmri.jmrix.powerline.SerialAddress(this.getSystemConnectionMemo()));
147    }
148
149    // base class methods for the SerialPortController interface
150    @Override
151    public DataInputStream getInputStream() {
152        if (!opened) {
153            log.error("getInputStream called before load(), stream not available");
154            return null;
155        }
156        return new DataInputStream(serialStream);
157    }
158
159    @Override
160    public DataOutputStream getOutputStream() {
161        if (!opened) {
162            log.error("getOutputStream called before load(), stream not available");
163        }
164        try {
165            return new DataOutputStream(activeSerialPort.getOutputStream());
166        } catch (java.io.IOException e) {
167            log.error("getOutputStream exception: {}", e.getMessage());
168        }
169        return null;
170    }
171
172    @Override
173    public boolean status() {
174        return opened;
175    }
176
177    String[] stdOption1Values = new String[]{"CM11", "CP290", "Insteon 2412S"}; // NOI18N
178
179    /**
180     * Local method to do specific port configuration
181     * @throws purejavacomm.UnsupportedCommOperationException used if invalid parms
182     */
183    protected void setSerialPort() throws UnsupportedCommOperationException {
184        // find the baud rate value, configure comm options
185        int baud = currentBaudNumber(mBaudRate);
186
187        // check for specific port type
188        String opt1 = getOptionState(option1Name);
189        if (opt1.equals("CM11")) {
190            // leave as 4800 baud
191        } else if (opt1.equals("CP290")) {
192            // set to 600 baud
193            baud = 600;
194        } else if (opt1.equals("Insteon 2412S")) {
195            // set to 19200 baud
196            baud = 19200;
197        }
198
199        activeSerialPort.setSerialPortParams(baud, SerialPort.DATABITS_8,
200                SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
201
202        // set RTS high, DTR high
203        // find and configure flow control
204        int flow = SerialPort.FLOWCONTROL_NONE; // default
205        configureLeadsAndFlowControl(activeSerialPort, flow);
206    }
207
208    /**
209     * {@inheritDoc}
210     */
211    @Override
212    public String[] validBaudRates() {
213        return Arrays.copyOf(validSpeeds, validSpeeds.length);
214    }
215
216    /**
217     * {@inheritDoc}
218     */
219    @Override
220    public int[] validBaudNumbers() {
221        return Arrays.copyOf(validSpeedValues, validSpeedValues.length);
222    }
223
224    protected String[] validSpeeds = new String[]{Bundle.getMessage("BaudAutomatic")};
225    protected int[] validSpeedValues = new int[]{4800};
226
227    @Override
228    public int defaultBaudIndex() {
229        return 0;
230    }
231
232    // private control members
233    private boolean opened = false;
234    InputStream serialStream = null;
235
236    private final static Logger log = LoggerFactory.getLogger(SerialDriverAdapter.class);
237
238}